blob: dbe434af25290087f10ed1967ee101c3bb901408 [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
Michal Vasko7a20d2e2021-05-19 16:40:23 +020036#include "compat.h"
romanf578cd52023-10-19 09:47:40 +020037#include "config.h"
38#include "log_p.h"
39#include "messages_p.h"
40#include "messages_server.h"
41#include "server_config_p.h"
42#include "session.h"
43#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010044#include "session_server.h"
Michal Vasko0bdf70b2019-06-24 19:20:20 +020045#include "session_server_ch.h"
roman9225e912024-04-05 12:33:09 +020046#include "session_wrapper.h"
47
48#ifdef NC_ENABLED_SSH_TLS
49#include <curl/curl.h>
50#include <libssh/libssh.h>
51#endif
Michal Vasko086311b2016-01-08 09:53:11 +010052
Michal Vaskob48aa812016-01-18 14:13:09 +010053struct nc_server_opts server_opts = {
romanf578cd52023-10-19 09:47:40 +020054 .config_lock = PTHREAD_RWLOCK_INITIALIZER,
55 .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER,
Michal Vaskocf898172024-01-15 15:04:28 +010056 .idle_timeout = 180, /**< default idle timeout (not in config for UNIX socket) */
Michal Vaskob48aa812016-01-18 14:13:09 +010057};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010058
fanchanghu966f2de2016-07-21 02:28:57 -040059static nc_rpc_clb global_rpc_clb = NULL;
60
roman423cc0d2023-11-24 11:29:47 +010061#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010062/**
63 * @brief Lock CH client structures for reading and lock the specific client.
64 *
65 * @param[in] name Name of the CH client.
66 * @return CH client, NULL if not found.
67 */
68static struct nc_ch_client *
69nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010070{
71 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020072 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020073
Michal Vasko6f865982023-11-21 12:10:42 +010074 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020075
Michal Vasko2e6defd2016-10-07 15:48:15 +020076 /* READ LOCK */
77 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
78
79 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020080 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020081 client = &server_opts.ch_clients[i];
82 break;
83 }
84 }
85
86 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020087 /* READ UNLOCK */
88 pthread_rwlock_unlock(&server_opts.ch_client_lock);
89 } else {
90 /* CH CLIENT LOCK */
91 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020092 }
93
Michal Vasko6f865982023-11-21 12:10:42 +010094 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020095}
96
Michal Vasko6f865982023-11-21 12:10:42 +010097/**
98 * @brief Unlock CH client strcutures and the specific client.
99 *
100 * @param[in] endpt Locked CH client structure.
101 */
102static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200103nc_server_ch_client_unlock(struct nc_ch_client *client)
104{
105 /* CH CLIENT UNLOCK */
106 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100107
108 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200109 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100110}
Michal Vasko086311b2016-01-08 09:53:11 +0100111
roman423cc0d2023-11-24 11:29:47 +0100112#endif /* NC_ENABLED_SSH_TLS */
113
roman78df0fa2023-11-02 10:33:57 +0100114int
115nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
116{
117 uint16_t i;
118
119 for (i = 0; i < server_opts.endpt_count; i++) {
120 if (!strcmp(name, server_opts.endpts[i].name)) {
121 *endpt = &server_opts.endpts[i];
122 return 0;
123 }
124 }
125
126 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
127 return 1;
128}
129
Michal Vasko1a38c862016-01-15 15:50:07 +0100130API void
131nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
132{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200133 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200134 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200135 return;
136 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200137 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100138 return;
139 }
140
Michal Vasko142cfea2017-08-07 10:12:11 +0200141 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
142 session->killed_by = 0;
143 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100144 session->term_reason = reason;
145}
146
Michal Vasko142cfea2017-08-07 10:12:11 +0200147API void
148nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
149{
150 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200151 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200152 return;
153 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200154 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200155 return;
156 }
157
158 session->killed_by = sid;
159}
160
161API void
162nc_session_set_status(struct nc_session *session, NC_STATUS status)
163{
164 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200165 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200166 return;
167 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200168 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200169 return;
170 }
171
172 session->status = status;
173}
174
romanf578cd52023-10-19 09:47:40 +0200175API int
176nc_server_init_ctx(struct ly_ctx **ctx)
177{
178 int new_ctx = 0, i, ret = 0;
179 struct lys_module *module;
180 /* all features */
181 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
182 /* all features (module has no features) */
183 const char *ietf_netconf_monitoring_features[] = {NULL};
184
185 NC_CHECK_ARG_RET(NULL, ctx, 1);
186
187 if (!*ctx) {
188 /* context not given, create a new one */
189 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
190 ERR(NULL, "Couldn't create new libyang context.\n");
191 ret = 1;
192 goto cleanup;
193 }
194 new_ctx = 1;
195 }
196
197 if (new_ctx) {
198 /* new context created, implement both modules */
199 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
200 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
201 ret = 1;
202 goto cleanup;
203 }
204
205 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
206 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
207 ret = 1;
208 goto cleanup;
209 }
210
211 goto cleanup;
212 }
213
214 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
215 if (module) {
216 /* ietf-netconf module is present, check features */
217 for (i = 0; ietf_netconf_features[i]; i++) {
218 if (lys_feature_value(module, ietf_netconf_features[i])) {
219 /* feature not found, enable all of them */
220 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
221 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
222 ret = 1;
223 goto cleanup;
224 }
225
226 break;
227 }
228 }
229 } else {
230 /* ietf-netconf module not found, add it */
231 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
232 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
233 ret = 1;
234 goto cleanup;
235 }
236 }
237
238 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
239 if (!module) {
240 /* ietf-netconf-monitoring module not found, add it */
241 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
242 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
243 ret = 1;
244 goto cleanup;
245 }
246 }
247
248cleanup:
249 if (new_ctx && ret) {
250 ly_ctx_destroy(*ctx);
251 *ctx = NULL;
252 }
253 return ret;
254}
255
roman96c27f92023-11-02 11:09:46 +0100256#ifdef NC_ENABLED_SSH_TLS
257
roman450c00b2023-11-02 10:31:45 +0100258API void
259nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
260 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
261 void *new_session_cb_data)
262{
263 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
264
265 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
266 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
267 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
268 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
269 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
270}
271
roman96c27f92023-11-02 11:09:46 +0100272#endif
273
Michal Vasko086311b2016-01-08 09:53:11 +0100274int
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200275nc_sock_listen_inet(const char *address, uint16_t port)
Michal Vasko086311b2016-01-08 09:53:11 +0100276{
Michal Vasko06c860d2018-07-09 16:08:52 +0200277 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100278 int is_ipv4, sock;
279 struct sockaddr_storage saddr;
280
281 struct sockaddr_in *saddr4;
282 struct sockaddr_in6 *saddr6;
283
Michal Vasko086311b2016-01-08 09:53:11 +0100284 if (!strchr(address, ':')) {
285 is_ipv4 = 1;
286 } else {
287 is_ipv4 = 0;
288 }
289
290 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
291 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200292 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100293 goto fail;
294 }
295
Michal Vaskobe52dc22018-10-17 09:28:17 +0200296 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200297 opt = 1;
298 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200299 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200300 goto fail;
301 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100302 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200303 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100304 goto fail;
305 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200306
Michal Vaskof22d5ff2020-04-15 11:10:27 +0200307 memset(&saddr, 0, sizeof(struct sockaddr_storage));
Michal Vasko086311b2016-01-08 09:53:11 +0100308 if (is_ipv4) {
309 saddr4 = (struct sockaddr_in *)&saddr;
310
311 saddr4->sin_family = AF_INET;
312 saddr4->sin_port = htons(port);
313
314 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200315 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100316 goto fail;
317 }
318
319 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200320 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100321 goto fail;
322 }
323
324 } else {
325 saddr6 = (struct sockaddr_in6 *)&saddr;
326
327 saddr6->sin6_family = AF_INET6;
328 saddr6->sin6_port = htons(port);
329
330 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200331 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100332 goto fail;
333 }
334
335 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200336 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100337 goto fail;
338 }
339 }
340
Michal Vaskofb89d772016-01-08 12:25:35 +0100341 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200342 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100343 goto fail;
344 }
Michal Vasko086311b2016-01-08 09:53:11 +0100345 return sock;
346
347fail:
348 if (sock > -1) {
349 close(sock);
350 }
351
352 return -1;
353}
354
Michal Vaskoc429a8e2024-01-15 15:04:57 +0100355/**
356 * @brief Create a listening socket (AF_UNIX).
357 *
358 * @param[in] opts The server options (unix permissions and address of the socket).
359 * @return Listening socket, -1 on error.
360 */
361static int
roman83683fb2023-02-24 09:15:23 +0100362nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200363{
364 struct sockaddr_un sun;
365 int sock = -1;
366
roman83683fb2023-02-24 09:15:23 +0100367 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
368 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 +0200369 goto fail;
370 }
371
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200372 sock = socket(AF_UNIX, SOCK_STREAM, 0);
373 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200374 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200375 goto fail;
376 }
377
378 memset(&sun, 0, sizeof(sun));
379 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100380 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200381
382 unlink(sun.sun_path);
383 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100384 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200385 goto fail;
386 }
387
388 if (opts->mode != (mode_t)-1) {
389 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200390 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200391 goto fail;
392 }
393 }
394
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200395 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200396 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200397 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200398 goto fail;
399 }
400 }
401
402 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100403 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200404 goto fail;
405 }
406
407 return sock;
408
409fail:
410 if (sock > -1) {
411 close(sock);
412 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200413 return -1;
414}
415
aPiecek90ff0242021-02-14 14:58:01 +0100416/**
417 * @brief Evaluate socket name for AF_UNIX socket.
418 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
419 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
420 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
421 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
422 * @return -1 in case of error. Parameter host is set to NULL.
423 */
424static int
425sock_host_unix(int acc_sock_fd, char **host)
426{
427 char *sun_path;
428 struct sockaddr_storage saddr;
429 socklen_t addr_len;
430
431 *host = NULL;
432 saddr.ss_family = AF_UNIX;
433 addr_len = sizeof(saddr);
434
435 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200436 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100437 return -1;
438 }
439
440 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
441 if (!sun_path) {
442 /* stream socket is unnamed */
443 return 0;
444 }
445
roman3a95bb22023-10-26 11:07:17 +0200446 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100447
448 return 0;
449}
450
451/**
452 * @brief Evaluate socket name and port number for AF_INET socket.
453 * @param[in] addr is pointing to structure filled by accept function which was successful.
454 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
455 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
456 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
457 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
458 */
459static int
460sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
461{
462 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200463 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100464
aPiecek3da9b342021-02-18 15:00:03 +0100465 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100466 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100467 free(*host);
468 *host = NULL;
469 return -1;
470 }
471
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200472 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100473
474 return 0;
475}
476
477/**
478 * @brief Evaluate socket name and port number for AF_INET6 socket.
479 * @param[in] addr is pointing to structure filled by accept function which was successful.
480 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
481 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
482 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
483 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
484 */
485static int
486sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
487{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200488 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200489 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100490
aPiecek3da9b342021-02-18 15:00:03 +0100491 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100492 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100493 free(*host);
494 *host = NULL;
495 return -1;
496 }
497
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200498 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100499
500 return 0;
501}
502
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200503int
Michal Vasko6f865982023-11-21 12:10:42 +0100504nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
505 uint16_t *port, uint16_t *idx)
Michal Vasko086311b2016-01-08 09:53:11 +0100506{
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200507 uint16_t i, j, pfd_count, client_port;
508 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100509 struct pollfd *pfd;
510 struct sockaddr_storage saddr;
511 socklen_t saddr_len = sizeof(saddr);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200512 int ret, client_sock, sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100513
514 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200515 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100516
romanf578cd52023-10-19 09:47:40 +0200517 /* LOCK */
518 pthread_mutex_lock(bind_lock);
519
Michal Vaskoac2f6182017-01-30 14:32:03 +0100520 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200521 if (binds[i].sock < 0) {
522 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200523 continue;
524 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200525 if (binds[i].pollin) {
526 binds[i].pollin = 0;
527 /* leftover pollin */
528 sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100529 break;
530 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100531 pfd[pfd_count].fd = binds[i].sock;
532 pfd[pfd_count].events = POLLIN;
533 pfd[pfd_count].revents = 0;
534
535 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100536 }
537
Michal Vasko0a3f3752016-10-13 14:58:38 +0200538 if (sock == -1) {
539 /* poll for a new connection */
Michal Vasko63b92d62024-04-29 10:04:56 +0200540 ret = nc_poll(pfd, pfd_count, timeout);
541 if (ret < 1) {
542 free(pfd);
Michal Vaskof54cd352017-02-22 13:42:02 +0100543
romanf578cd52023-10-19 09:47:40 +0200544 /* UNLOCK */
545 pthread_mutex_unlock(bind_lock);
Michal Vasko63b92d62024-04-29 10:04:56 +0200546
547 return ret;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200548 }
Michal Vasko086311b2016-01-08 09:53:11 +0100549
Michal Vaskoac2f6182017-01-30 14:32:03 +0100550 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
551 /* adjust i so that indices in binds and pfd always match */
552 while (binds[i].sock != pfd[j].fd) {
553 ++i;
554 }
555
556 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200557 --ret;
558
559 if (!ret) {
560 /* the last socket with an event, use it */
Michal Vaskoac2f6182017-01-30 14:32:03 +0100561 sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200562 break;
563 } else {
564 /* just remember the event for next time */
565 binds[i].pollin = 1;
566 }
567 }
Michal Vasko086311b2016-01-08 09:53:11 +0100568 }
569 }
570 free(pfd);
Michal Vasko086311b2016-01-08 09:53:11 +0100571 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100572 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200573 /* UNLOCK */
574 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100575 return -1;
576 }
577
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200578 /* accept connection */
579 client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
580 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200581 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200582 /* UNLOCK */
583 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100584 return -1;
585 }
586
Michal Vasko0190bc32016-03-02 15:47:49 +0100587 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200588 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200589 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200590 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100591 }
592
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200593 /* learn information about the client end */
594 if (saddr.ss_family == AF_UNIX) {
595 if (sock_host_unix(client_sock, &client_address)) {
596 goto fail;
597 }
598 client_port = 0;
599 } else if (saddr.ss_family == AF_INET) {
600 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
601 goto fail;
602 }
603 } else if (saddr.ss_family == AF_INET6) {
604 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
605 goto fail;
606 }
607 } else {
608 ERR(NULL, "Source host of an unknown protocol family.");
609 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100610 }
Michal Vasko086311b2016-01-08 09:53:11 +0100611
aPiecek90ff0242021-02-14 14:58:01 +0100612 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200613 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100614 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200615 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 +0100616 }
617
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200618 if (host) {
619 *host = client_address;
620 } else {
621 free(client_address);
622 }
623 if (port) {
624 *port = client_port;
625 }
626 if (idx) {
627 *idx = i;
628 }
romanf578cd52023-10-19 09:47:40 +0200629 /* UNLOCK */
630 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200631 return client_sock;
632
633fail:
634 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200635 /* UNLOCK */
636 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200637 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100638}
639
Michal Vasko238b6c12021-12-14 15:14:09 +0100640API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200641nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100642{
Michal Vasko77367452021-02-16 16:32:18 +0100643 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100644 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100645 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100646 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100647 const struct lysp_submodule *submodule = NULL;
648 struct lyd_node *child, *err, *data = NULL;
649 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100650
Michal Vasko77367452021-02-16 16:32:18 +0100651 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100652 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200653 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100654 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200655 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200656 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100657 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500658 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100659 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200660 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100661 }
662 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200663 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100664
Michal Vasko77367452021-02-16 16:32:18 +0100665 /* check revision */
666 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100667 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100668 nc_err_set_msg(err, "The requested version is not supported.", "en");
669 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100670 }
671
Michal Vasko77367452021-02-16 16:32:18 +0100672 if (revision) {
673 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100674 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100675 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100676 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100677 }
678 } else {
679 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100680 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100681 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100682 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100683 }
684 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100685 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100686 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200687 }
Michal Vasko77367452021-02-16 16:32:18 +0100688 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100689 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200690 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100691 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100692 }
693
694 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100695 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100696 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100697 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100698 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100699 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100700 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100701 nc_err_set_msg(err, "The requested format is not supported.", "en");
702 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100703 }
Michal Vasko77367452021-02-16 16:32:18 +0100704
705 /* print */
706 ly_out_new_memory(&model_data, 0, &out);
707 if (module) {
708 lys_print_module(out, module, outformat, 0, 0);
709 } else {
710 lys_print_submodule(out, submodule, outformat, 0, 0);
711 }
712 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200713 if (!model_data) {
714 ERRINT;
715 return NULL;
716 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100717
Michal Vasko9b1a9522021-03-15 16:24:26 +0100718 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100719 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100720 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100721 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200722 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100723 return NULL;
724 }
Michal Vasko58791da2024-02-26 13:52:59 +0100725 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 +0100726 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100727 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100728 lyd_free_tree(data);
729 return NULL;
730 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100731
Radek Krejci36dfdb32016-09-01 16:56:35 +0200732 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100733}
734
Michal Vasko238b6c12021-12-14 15:14:09 +0100735API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100736nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100737{
Michal Vasko428087d2016-01-14 16:04:28 +0100738 session->term_reason = NC_SESSION_TERM_CLOSED;
739 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100740}
741
Michal Vasko93224072021-11-09 12:14:28 +0100742/**
743 * @brief Initialize a context with default RPC callbacks if none are set.
744 *
745 * @param[in] ctx Context to initialize.
746 */
747static void
romanf578cd52023-10-19 09:47:40 +0200748nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100749{
Michal Vasko77367452021-02-16 16:32:18 +0100750 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100751
Michal Vasko238b6c12021-12-14 15:14:09 +0100752 if (global_rpc_clb) {
753 /* expect it to handle these RPCs as well */
754 return;
755 }
756
Michal Vasko05ba9df2016-01-13 14:40:27 +0100757 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100758 rpc = NULL;
759 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
760 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
761 }
Michal Vasko88639e92017-08-03 14:38:10 +0200762 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100763 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100764 }
765
Michal Vasko93224072021-11-09 12:14:28 +0100766 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100767 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200768 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100769 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100770 }
Michal Vasko93224072021-11-09 12:14:28 +0100771}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100772
Michal Vasko93224072021-11-09 12:14:28 +0100773API int
774nc_server_init(void)
775{
Michal Vasko29f2f022024-03-13 09:06:48 +0100776 pthread_rwlockattr_t *attr_p = NULL;
Michal Vasko93224072021-11-09 12:14:28 +0100777 int r;
778
romand82caf12024-03-05 14:21:39 +0100779 ATOMIC_STORE_RELAXED(server_opts.new_session_id, 1);
780 ATOMIC_STORE_RELAXED(server_opts.new_client_id, 1);
Michal Vaskob48aa812016-01-18 14:13:09 +0100781
Michal Vasko93224072021-11-09 12:14:28 +0100782#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
Michal Vasko29f2f022024-03-13 09:06:48 +0100783 pthread_rwlockattr_t attr;
784
Michal Vasko93224072021-11-09 12:14:28 +0100785 if ((r = pthread_rwlockattr_init(&attr))) {
786 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
787 goto error;
788 }
789 attr_p = &attr;
790 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
791 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
792 goto error;
793 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700794#endif
Michal Vasko93224072021-11-09 12:14:28 +0100795
romanf578cd52023-10-19 09:47:40 +0200796 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100797 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
798 goto error;
799 }
800 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
801 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
802 goto error;
803 }
804
805 if (attr_p) {
806 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000807 }
romanf578cd52023-10-19 09:47:40 +0200808
809#ifdef NC_ENABLED_SSH_TLS
810 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
811 ERR(NULL, "%s: failed to init CURL.", __func__);
812 goto error;
813 }
Michal Vasko5788c802024-05-03 16:14:40 +0200814
815 /* optional for dynamic library, mandatory for static */
816 if (ssh_init()) {
817 ERR(NULL, "%s: failed to init libssh.", __func__);
818 goto error;
819 }
romanf578cd52023-10-19 09:47:40 +0200820#endif
821
822 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
823 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
824 goto error;
825 }
826
Michal Vasko086311b2016-01-08 09:53:11 +0100827 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100828
829error:
830 if (attr_p) {
831 pthread_rwlockattr_destroy(attr_p);
832 }
833 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100834}
835
Michal Vaskob48aa812016-01-18 14:13:09 +0100836API void
837nc_server_destroy(void)
838{
romana2ff4ef2024-01-19 14:41:46 +0100839 uint32_t i, endpt_count;
Radek Krejci658782b2016-12-04 22:04:55 +0100840
841 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100842 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100843 }
844 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200845 server_opts.capabilities = NULL;
846 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200847 if (server_opts.content_id_data && server_opts.content_id_data_free) {
848 server_opts.content_id_data_free(server_opts.content_id_data);
849 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200850
romanf578cd52023-10-19 09:47:40 +0200851 nc_server_config_listen(NULL, NC_OP_DELETE);
852 nc_server_config_ch(NULL, NC_OP_DELETE);
853
romana2ff4ef2024-01-19 14:41:46 +0100854 endpt_count = server_opts.endpt_count;
855 for (i = 0; i < endpt_count; i++) {
856 if (server_opts.endpts[i].ti == NC_TI_UNIX) {
857 _nc_server_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]);
858 }
859 }
860
romanf578cd52023-10-19 09:47:40 +0200861 pthread_mutex_destroy(&server_opts.bind_lock);
862
863#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100864 free(server_opts.authkey_path_fmt);
865 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100866 free(server_opts.pam_config_name);
867 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200868 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
869 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
870 }
871 server_opts.interactive_auth_data = NULL;
872 server_opts.interactive_auth_data_free = NULL;
873
romanf578cd52023-10-19 09:47:40 +0200874 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
875 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
876 curl_global_cleanup();
Michal Vasko5788c802024-05-03 16:14:40 +0200877 ssh_finalize();
romanf578cd52023-10-19 09:47:40 +0200878#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100879}
880
Michal Vasko086311b2016-01-08 09:53:11 +0100881API int
882nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
883{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200884 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200885 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200886 return -1;
887 } 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 +0200888 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100889 return -1;
890 }
891
romanf578cd52023-10-19 09:47:40 +0200892 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
893 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100894 return 0;
895}
896
Michal Vasko1a38c862016-01-15 15:50:07 +0100897API void
Michal Vasko55f03972016-04-13 08:56:01 +0200898nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
899{
900 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200901 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200902 return;
903 }
904
905 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200906 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200907 }
908 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200909 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200910 }
911}
912
Michal Vasko55f03972016-04-13 08:56:01 +0200913API int
Radek Krejci658782b2016-12-04 22:04:55 +0100914nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200915{
Michal Vasko93224072021-11-09 12:14:28 +0100916 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100917
918 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200919 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100920 return EXIT_FAILURE;
921 }
922
Michal Vasko93224072021-11-09 12:14:28 +0100923 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200924 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100925 server_opts.capabilities = mem;
926
927 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
928 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100929
930 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200931}
932
Michal Vasko1a38c862016-01-15 15:50:07 +0100933API void
Michal Vasko1440a742021-03-31 11:11:03 +0200934nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
935 void (*free_user_data)(void *user_data))
936{
937 server_opts.content_id_clb = content_id_clb;
938 server_opts.content_id_data = user_data;
939 server_opts.content_id_data_free = free_user_data;
940}
941
Michal Vasko71090fc2016-05-24 16:37:28 +0200942API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100943nc_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 +0100944{
Michal Vasko71090fc2016-05-24 16:37:28 +0200945 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200946 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200947
romand82caf12024-03-05 14:21:39 +0100948 NC_CHECK_ARG_RET(NULL, ctx, username, fdin >= 0, fdout >= 0, session, NC_MSG_ERROR);
romanf578cd52023-10-19 09:47:40 +0200949
romand82caf12024-03-05 14:21:39 +0100950 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
Michal Vasko086311b2016-01-08 09:53:11 +0100951
Michal Vasko93224072021-11-09 12:14:28 +0100952 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200953 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100954
Michal Vasko086311b2016-01-08 09:53:11 +0100955 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200956 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200957 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100958 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100959
Michal Vasko086311b2016-01-08 09:53:11 +0100960 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100961 (*session)->ti_type = NC_TI_FD;
962 (*session)->ti.fd.in = fdin;
963 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100964
Michal Vasko93224072021-11-09 12:14:28 +0100965 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100966 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100967 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100968
Michal Vaskob48aa812016-01-18 14:13:09 +0100969 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200970 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100971
Michal Vasko086311b2016-01-08 09:53:11 +0100972 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200973 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200974 if (msgtype != NC_MSG_HELLO) {
975 nc_session_free(*session, NULL);
976 *session = NULL;
977 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100978 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200979
Michal Vaskod8a74192023-02-06 15:51:50 +0100980 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200981 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100982 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100983 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200984
Michal Vasko1a38c862016-01-15 15:50:07 +0100985 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100986
Michal Vasko71090fc2016-05-24 16:37:28 +0200987 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100988}
Michal Vasko9e036d52016-01-08 10:49:26 +0100989
Michal Vaskob30b99c2016-07-26 11:35:43 +0200990static void
Michal Vasko74c345f2018-02-07 10:37:11 +0100991nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
992{
993 uint8_t q_last;
994
995 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
996 ERRINT;
997 return;
998 }
999
1000 /* get a unique queue value (by adding 1 to the last added value, if any) */
1001 if (ps->queue_len) {
1002 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1003 *id = ps->queue[q_last] + 1;
1004 } else {
1005 *id = 0;
1006 }
1007
1008 /* add the id into the queue */
1009 ++ps->queue_len;
1010 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1011 ps->queue[q_last] = *id;
1012}
1013
1014static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001015nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1016{
Michal Vasko74c345f2018-02-07 10:37:11 +01001017 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001018
1019 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001020 /* get the actual queue idx */
1021 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001022
1023 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001024 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001025 /* another equal value, simply cannot be */
1026 ERRINT;
1027 }
Michal Vaskod8340032018-02-12 14:41:00 +01001028 if (found == 2) {
1029 /* move the following values */
1030 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1031 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001032 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001033 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001034 if (i == 0) {
1035 found = 1;
1036 } else {
1037 /* this is not okay, our id is in the middle of the queue */
1038 found = 2;
1039 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001040 }
1041 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001042 if (!found) {
1043 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001044 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001045 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001046
Michal Vasko103fe632018-02-12 16:37:45 +01001047 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001048 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001049 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001050 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1051 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001052}
1053
Michal Vaskof04a52a2016-04-07 10:52:10 +02001054int
Michal Vasko26043172016-07-26 14:08:59 +02001055nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001056{
1057 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001058 struct timespec ts;
1059
Michal Vaskobe86fe32016-04-07 10:43:03 +02001060 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001061 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001062 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001063 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001064 return -1;
1065 }
1066
Michal Vasko74c345f2018-02-07 10:37:11 +01001067 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001068 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001069 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001070 pthread_mutex_unlock(&ps->lock);
1071 return -1;
1072 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001073
1074 /* add ourselves into the queue */
1075 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001076 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 +02001077 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001078
1079 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001080 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001081 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001082
Michal Vaskod8a74192023-02-06 15:51:50 +01001083 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001084 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301085 /**
1086 * This may happen when another thread releases the lock and broadcasts the condition
1087 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1088 * but when actually this thread was ready for condition.
1089 */
preetbhansali629dfc42018-12-17 16:04:40 +05301090 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301091 break;
1092 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001093
Michal Vasko05532772021-06-03 12:12:38 +02001094 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001095 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001096 nc_ps_queue_remove_id(ps, *id);
1097 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001098 return -1;
1099 }
1100 }
1101
Michal Vaskobe86fe32016-04-07 10:43:03 +02001102 /* UNLOCK */
1103 pthread_mutex_unlock(&ps->lock);
1104
1105 return 0;
1106}
1107
Michal Vaskof04a52a2016-04-07 10:52:10 +02001108int
Michal Vasko26043172016-07-26 14:08:59 +02001109nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001110{
1111 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001112
1113 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001114 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001115 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001116 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001117 ret = -1;
1118 }
1119
Michal Vaskob30b99c2016-07-26 11:35:43 +02001120 /* we must be the first, it was our turn after all, right? */
1121 if (ps->queue[ps->queue_begin] != id) {
1122 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001123 /* UNLOCK */
1124 if (!ret) {
1125 pthread_mutex_unlock(&ps->lock);
1126 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001127 return -1;
1128 }
1129
Michal Vaskobe86fe32016-04-07 10:43:03 +02001130 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001131 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001132 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 +02001133 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001134
1135 /* broadcast to all other threads that the queue moved */
1136 pthread_cond_broadcast(&ps->cond);
1137
Michal Vaskobe86fe32016-04-07 10:43:03 +02001138 /* UNLOCK */
1139 if (!ret) {
1140 pthread_mutex_unlock(&ps->lock);
1141 }
1142
1143 return ret;
1144}
1145
Michal Vasko428087d2016-01-14 16:04:28 +01001146API struct nc_pollsession *
1147nc_ps_new(void)
1148{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001149 struct nc_pollsession *ps;
1150
1151 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001152 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001153 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001154 pthread_mutex_init(&ps->lock, NULL);
1155
1156 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001157}
1158
1159API void
1160nc_ps_free(struct nc_pollsession *ps)
1161{
fanchanghu3d4e7212017-08-09 09:42:30 +08001162 uint16_t i;
1163
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001164 if (!ps) {
1165 return;
1166 }
1167
Michal Vaskobe86fe32016-04-07 10:43:03 +02001168 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001169 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001170 }
1171
fanchanghu3d4e7212017-08-09 09:42:30 +08001172 for (i = 0; i < ps->session_count; i++) {
1173 free(ps->sessions[i]);
1174 }
1175
Michal Vasko428087d2016-01-14 16:04:28 +01001176 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001177 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001178 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001179
Michal Vasko428087d2016-01-14 16:04:28 +01001180 free(ps);
1181}
1182
1183API int
1184nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1185{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001186 uint8_t q_id;
1187
romanf578cd52023-10-19 09:47:40 +02001188 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001189
Michal Vasko48a63ed2016-03-01 09:48:21 +01001190 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001191 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001192 return -1;
1193 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001194
Michal Vasko428087d2016-01-14 16:04:28 +01001195 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001196 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001197 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001198 ERRMEM;
1199 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001200 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001201 return -1;
1202 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001203 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1204 if (!ps->sessions[ps->session_count - 1]) {
1205 ERRMEM;
1206 --ps->session_count;
1207 /* UNLOCK */
1208 nc_ps_unlock(ps, q_id, __func__);
1209 return -1;
1210 }
1211 ps->sessions[ps->session_count - 1]->session = session;
1212 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001213
Michal Vasko48a63ed2016-03-01 09:48:21 +01001214 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001215 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001216}
1217
Michal Vasko48a63ed2016-03-01 09:48:21 +01001218static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001219_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001220{
1221 uint16_t i;
1222
Radek Krejcid5f978f2016-03-03 13:14:45 +01001223 if (index >= 0) {
1224 i = (uint16_t)index;
1225 goto remove;
1226 }
Michal Vasko428087d2016-01-14 16:04:28 +01001227 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001228 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001229remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001230 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001231 if (i <= ps->session_count) {
1232 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001233 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001234 }
1235 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001236 free(ps->sessions);
1237 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001238 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001239 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001240 return 0;
1241 }
1242 }
1243
Michal Vaskof0537d82016-01-29 14:42:38 +01001244 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001245}
1246
Michal Vasko48a63ed2016-03-01 09:48:21 +01001247API int
1248nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1249{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001250 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001251 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001252
romanf578cd52023-10-19 09:47:40 +02001253 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001254
1255 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001256 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001257 return -1;
1258 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001259
Radek Krejcid5f978f2016-03-03 13:14:45 +01001260 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001261
1262 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001263 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001264
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001265 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001266}
1267
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001268API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001269nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001270{
1271 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001272 struct nc_session *ret = NULL;
1273
romanf578cd52023-10-19 09:47:40 +02001274 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001275
1276 /* LOCK */
1277 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1278 return NULL;
1279 }
1280
Michal Vasko4871c9d2017-10-09 14:48:39 +02001281 if (idx < ps->session_count) {
1282 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001283 }
1284
1285 /* UNLOCK */
1286 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1287
1288 return ret;
1289}
1290
Michal Vasko3ec3b112022-07-21 12:32:33 +02001291API struct nc_session *
1292nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1293{
1294 uint8_t q_id;
1295 uint16_t i;
1296 struct nc_session *ret = NULL;
1297
romanf578cd52023-10-19 09:47:40 +02001298 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001299
1300 /* LOCK */
1301 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1302 return NULL;
1303 }
1304
1305 for (i = 0; i < ps->session_count; ++i) {
1306 if (match_cb(ps->sessions[i]->session, cb_data)) {
1307 ret = ps->sessions[i]->session;
1308 break;
1309 }
1310 }
1311
1312 /* UNLOCK */
1313 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1314
1315 return ret;
1316}
1317
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001318API uint16_t
1319nc_ps_session_count(struct nc_pollsession *ps)
1320{
Michal Vasko47003942019-03-14 12:25:23 +01001321 uint8_t q_id;
1322 uint16_t session_count;
1323
romanf578cd52023-10-19 09:47:40 +02001324 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001325
Michal Vasko47003942019-03-14 12:25:23 +01001326 /* LOCK (just for memory barrier so that we read the current value) */
1327 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1328 return 0;
1329 }
1330
1331 session_count = ps->session_count;
1332
1333 /* UNLOCK */
1334 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1335
1336 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001337}
1338
Michal Vasko77e83572022-07-21 15:31:15 +02001339static NC_MSG_TYPE
1340recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1341{
1342 struct lyd_attr *attr;
1343
1344 assert(envp && !envp->schema);
1345
1346 /* find the message-id attribute */
1347 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1348 if (!strcmp(attr->name.name, "message-id")) {
1349 break;
1350 }
1351 }
1352
1353 if (!attr) {
1354 ERR(session, "Received an <rpc> without a message-id.");
1355 return NC_MSG_REPLY_ERR_MSGID;
1356 }
1357
1358 return NC_MSG_RPC;
1359}
1360
Michal Vasko131120a2018-05-29 15:44:02 +02001361/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001362 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001363 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001364 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001365 * NC_PSPOLL_RPC
1366 */
1367static int
Michal Vasko131120a2018-05-29 15:44:02 +02001368nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001369{
Michal Vasko77367452021-02-16 16:32:18 +01001370 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001371 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001372 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001373 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001374
romanf578cd52023-10-19 09:47:40 +02001375 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1376
1377 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001378 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001379 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001380 }
1381
Michal Vasko93224072021-11-09 12:14:28 +01001382 *rpc = NULL;
1383
Michal Vasko77367452021-02-16 16:32:18 +01001384 /* get a message */
1385 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1386 if (r == -2) {
1387 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001388 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001389 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001390 }
1391 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001392 return NC_PSPOLL_ERROR;
1393 } else if (!r) {
1394 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001395 }
1396
Michal Vasko77367452021-02-16 16:32:18 +01001397 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001398 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001399
1400 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001401 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1402 /* check message-id */
1403 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1404 /* valid RPC */
1405 ret = NC_PSPOLL_RPC;
1406 } else {
1407 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001408 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1409 }
1410 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001411 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001412 if ((*rpc)->envp) {
1413 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001414 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
Michal Vasko58791da2024-02-26 13:52:59 +01001415 nc_err_set_msg(e, ly_err_last(session->ctx)->msg, "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001416 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001417 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001418 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1419 * the server (RFC 6241 sec. 3) */
1420 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001421 } else {
1422 /* at least set the return value */
1423 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001424 }
Michal Vasko77367452021-02-16 16:32:18 +01001425 }
1426
1427cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001428 if (reply) {
1429 /* send error reply */
1430 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1431 nc_server_reply_free(reply);
1432 if (r != NC_MSG_REPLY) {
1433 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1434 if (session->status != NC_STATUS_INVALID) {
1435 session->status = NC_STATUS_INVALID;
1436 session->term_reason = NC_SESSION_TERM_OTHER;
1437 }
1438 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001439
1440 /* bad RPC and an error reply sent */
1441 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001442 }
1443
Michal Vasko77367452021-02-16 16:32:18 +01001444 ly_in_free(msg, 1);
1445 if (ret != NC_PSPOLL_RPC) {
1446 nc_server_rpc_free(*rpc);
1447 *rpc = NULL;
1448 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001449 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001450}
1451
fanchanghu966f2de2016-07-21 02:28:57 -04001452API void
1453nc_set_global_rpc_clb(nc_rpc_clb clb)
1454{
1455 global_rpc_clb = clb;
1456}
1457
Radek Krejci93e80222016-10-03 13:34:25 +02001458API NC_MSG_TYPE
1459nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1460{
Michal Vasko131120a2018-05-29 15:44:02 +02001461 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001462
1463 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001464 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001465 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001466 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001467 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001468 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001469 return NC_MSG_ERROR;
1470 }
1471
Michal Vasko131120a2018-05-29 15:44:02 +02001472 /* we do not need RPC lock for this, IO lock will be acquired properly */
1473 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001474 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001475 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001476 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001477
Michal Vasko131120a2018-05-29 15:44:02 +02001478 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001479}
1480
Michal Vaskof9467762023-03-28 09:02:08 +02001481/**
1482 * @brief Send a reply acquiring IO lock as needed.
1483 * Session RPC lock must be held!
1484 *
1485 * @param[in] session Session to use.
1486 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1487 * @param[in] rpc RPC to sent.
1488 * @return 0 on success.
1489 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1490 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001491 */
1492static int
Michal Vasko93224072021-11-09 12:14:28 +01001493nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001494{
1495 nc_rpc_clb clb;
1496 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001497 const struct lysc_node *rpc_act = NULL;
1498 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001499 int ret = 0;
1500 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001501
Michal Vasko4a827e52016-03-03 10:59:00 +01001502 if (!rpc) {
1503 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001504 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001505 }
1506
Michal Vasko77367452021-02-16 16:32:18 +01001507 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001508 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001509 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001510 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001511 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001512 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001513 if (elem->schema->nodetype == LYS_ACTION) {
1514 rpc_act = elem->schema;
1515 break;
1516 }
Michal Vasko77367452021-02-16 16:32:18 +01001517 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001518 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001519 if (!rpc_act) {
1520 ERRINT;
1521 return NC_PSPOLL_ERROR;
1522 }
1523 }
1524
1525 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001526 if (!global_rpc_clb) {
1527 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001528 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 +03001529 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001530 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001531 }
Michal Vasko428087d2016-01-14 16:04:28 +01001532 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001533 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001534 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001535 }
1536
1537 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001538 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001539 }
Michal Vasko77367452021-02-16 16:32:18 +01001540 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001541 if (reply->type == NC_RPL_ERROR) {
1542 ret |= NC_PSPOLL_REPLY_ERROR;
1543 }
1544 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001545
Michal Vasko131120a2018-05-29 15:44:02 +02001546 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001547 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001548 ret |= NC_PSPOLL_ERROR;
1549 }
Michal Vasko428087d2016-01-14 16:04:28 +01001550
1551 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1552 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1553 session->status = NC_STATUS_INVALID;
1554 }
1555
Michal Vasko71090fc2016-05-24 16:37:28 +02001556 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001557}
1558
Michal Vaskof9467762023-03-28 09:02:08 +02001559/**
1560 * @brief Poll a session from pspoll acquiring IO lock as needed.
1561 * Session must be running and session RPC lock held!
1562 *
1563 * @param[in] session Session to use.
1564 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1565 * @param[in] now_mono Current monotonic timestamp.
1566 * @param[in,out] msg Message to fill in case of an error.
1567 * @return NC_PSPOLL_RPC if some application data are available.
1568 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1569 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1570 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1571 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1572 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001573 */
1574static int
Michal Vasko131120a2018-05-29 15:44:02 +02001575nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001576{
Michal Vasko9a327362017-01-11 11:31:46 +01001577 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001578 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001579
romanf578cd52023-10-19 09:47:40 +02001580#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001581 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001582 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001583#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001584
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001585 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001586 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001587 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001588 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001589 session->status = NC_STATUS_INVALID;
1590 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1591 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1592 }
1593
Michal Vasko131120a2018-05-29 15:44:02 +02001594 r = nc_session_io_lock(session, io_timeout, __func__);
1595 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001596 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001597 return NC_PSPOLL_ERROR;
1598 } else if (!r) {
1599 return NC_PSPOLL_TIMEOUT;
1600 }
1601
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001602 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001603#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02001604 case NC_TI_SSH:
romanf578cd52023-10-19 09:47:40 +02001605 ssh_msg = ssh_message_get(session->ti.libssh.session);
1606 if (ssh_msg) {
1607 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1608 if (session->ti.libssh.next) {
1609 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1610 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1611 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1612 /* new NETCONF SSH channel */
1613 ret = NC_PSPOLL_SSH_CHANNEL;
1614 break;
1615 }
1616 }
1617 if (new != session) {
1618 ssh_message_free(ssh_msg);
1619 break;
1620 }
1621 }
1622 if (!ret) {
1623 /* just some SSH message */
1624 ret = NC_PSPOLL_SSH_MSG;
1625 }
1626 ssh_message_free(ssh_msg);
1627
1628 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1629 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1630 */
1631 break;
1632 }
1633
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001634 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001635 if (r == SSH_EOF) {
1636 sprintf(msg, "SSH channel unexpected EOF");
1637 session->status = NC_STATUS_INVALID;
1638 session->term_reason = NC_SESSION_TERM_DROPPED;
1639 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1640 } else if (r == SSH_ERROR) {
1641 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001642 session->status = NC_STATUS_INVALID;
1643 session->term_reason = NC_SESSION_TERM_OTHER;
1644 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001645 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001646 /* no application data received */
1647 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001648 } else {
1649 /* we have some application data */
1650 ret = NC_PSPOLL_RPC;
1651 }
1652 break;
roman506354a2024-04-11 09:37:22 +02001653 case NC_TI_TLS:
romance435112024-04-23 15:12:09 +02001654 r = nc_tls_get_num_pending_bytes_wrap(session->ti.tls.session);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001655 if (!r) {
1656 /* no data pending in the SSL buffer, poll fd */
roman9225e912024-04-05 12:33:09 +02001657 pfd.fd = nc_tls_get_fd_wrap(session);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001658 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001659 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001660 ret = NC_PSPOLL_ERROR;
1661 break;
1662 }
1663 pfd.events = POLLIN;
1664 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001665 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001666
Michal Vasko63b92d62024-04-29 10:04:56 +02001667 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001668 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001669 session->status = NC_STATUS_INVALID;
1670 ret = NC_PSPOLL_ERROR;
1671 } else if (r > 0) {
1672 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001673 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001674 session->status = NC_STATUS_INVALID;
1675 session->term_reason = NC_SESSION_TERM_DROPPED;
1676 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1677 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001678 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001679 session->status = NC_STATUS_INVALID;
1680 session->term_reason = NC_SESSION_TERM_OTHER;
1681 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1682 } else {
1683 ret = NC_PSPOLL_RPC;
1684 }
1685 } else {
1686 ret = NC_PSPOLL_TIMEOUT;
1687 }
1688 } else {
1689 ret = NC_PSPOLL_RPC;
1690 }
1691 break;
romanf578cd52023-10-19 09:47:40 +02001692#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001693 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001694 case NC_TI_UNIX:
1695 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001696 pfd.events = POLLIN;
1697 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001698 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001699
Michal Vasko63b92d62024-04-29 10:04:56 +02001700 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001701 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001702 session->status = NC_STATUS_INVALID;
1703 ret = NC_PSPOLL_ERROR;
1704 } else if (r > 0) {
1705 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001706 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001707 session->status = NC_STATUS_INVALID;
1708 session->term_reason = NC_SESSION_TERM_DROPPED;
1709 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1710 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001711 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001712 session->status = NC_STATUS_INVALID;
1713 session->term_reason = NC_SESSION_TERM_OTHER;
1714 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1715 } else {
1716 ret = NC_PSPOLL_RPC;
1717 }
1718 } else {
1719 ret = NC_PSPOLL_TIMEOUT;
1720 }
1721 break;
1722 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001723 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001724 ret = NC_PSPOLL_ERROR;
1725 break;
1726 }
1727
Michal Vasko131120a2018-05-29 15:44:02 +02001728 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001729 return ret;
1730}
1731
1732API int
1733nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1734{
Michal Vasko443faa02022-10-20 09:09:03 +02001735 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001736 uint8_t q_id;
1737 uint16_t i, j;
1738 char msg[256];
1739 struct timespec ts_timeout, ts_cur;
1740 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001741 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001742 struct nc_server_rpc *rpc = NULL;
1743
romanf578cd52023-10-19 09:47:40 +02001744 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001745
Michal Vaskoade892d2017-02-22 13:40:35 +01001746 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001747 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001748 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001749 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001750
Michal Vaskoade892d2017-02-22 13:40:35 +01001751 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001752 nc_ps_unlock(ps, q_id, __func__);
1753 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001754 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001755
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001756 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001757 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001758 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001759 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001760 }
1761
1762 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001763 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001764 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001765 if (ps->last_event_session == ps->session_count - 1) {
1766 i = j = 0;
1767 } else {
1768 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001769 }
Michal Vasko9a327362017-01-11 11:31:46 +01001770 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001771 cur_ps_session = ps->sessions[i];
1772 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001773
Michal Vasko131120a2018-05-29 15:44:02 +02001774 /* SESSION RPC LOCK */
1775 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001776 if (r == -1) {
1777 ret = NC_PSPOLL_ERROR;
1778 } else if (r == 1) {
1779 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001780 switch (cur_ps_session->state) {
1781 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001782 if (cur_session->status == NC_STATUS_RUNNING) {
1783 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001784 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001785
Michal Vasko131120a2018-05-29 15:44:02 +02001786 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001787 switch (ret) {
1788 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001789 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001790 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001791 break;
1792 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001793 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001794 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001795 break;
1796 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001797#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001798 case NC_PSPOLL_SSH_CHANNEL:
1799 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001800#endif /* NC_ENABLED_SSH_TLS */
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_RPC:
1804 /* let's keep the state busy, we are not done with this session */
1805 break;
1806 }
1807 } else {
1808 /* session is not fine, let the caller know */
1809 ret = NC_PSPOLL_SESSION_TERM;
1810 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1811 ret |= NC_PSPOLL_SESSION_ERROR;
1812 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001813 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001814 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001815 break;
1816 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001817 /* it definitely should not be busy because we have the lock */
1818 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001819 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001820 break;
1821 case NC_PS_STATE_INVALID:
1822 /* we got it locked, but it will be freed, let it be */
1823 ret = NC_PSPOLL_TIMEOUT;
1824 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001825 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001826
Michal Vasko131120a2018-05-29 15:44:02 +02001827 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001828 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001829 /* SESSION RPC UNLOCK */
1830 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001831 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001832 } else {
1833 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001834 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001835 }
Michal Vasko428087d2016-01-14 16:04:28 +01001836
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001837 /* something happened */
1838 if (ret != NC_PSPOLL_TIMEOUT) {
1839 break;
1840 }
1841
Michal Vasko9a327362017-01-11 11:31:46 +01001842 if (i == ps->session_count - 1) {
1843 i = 0;
1844 } else {
1845 ++i;
1846 }
1847 } while (i != j);
1848
Michal Vaskoade892d2017-02-22 13:40:35 +01001849 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001850 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001851 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001852
Michal Vaskod8a74192023-02-06 15:51:50 +01001853 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001854 /* final timeout */
1855 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001856 }
Michal Vasko428087d2016-01-14 16:04:28 +01001857 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001858 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001859
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001860 /* do we want to return the session? */
1861 switch (ret) {
1862 case NC_PSPOLL_RPC:
1863 case NC_PSPOLL_SESSION_TERM:
1864 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001865#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001866 case NC_PSPOLL_SSH_CHANNEL:
1867 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001868#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001869 if (session) {
1870 *session = cur_session;
1871 }
1872 ps->last_event_session = i;
1873 break;
1874 default:
1875 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001876 }
Michal Vasko428087d2016-01-14 16:04:28 +01001877
Michal Vaskoade892d2017-02-22 13:40:35 +01001878 /* PS UNLOCK */
1879 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001880
Michal Vasko131120a2018-05-29 15:44:02 +02001881 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001882 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001883 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001884 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1885 if (cur_session->status != NC_STATUS_RUNNING) {
1886 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001887 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001888 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001889 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001890 }
1891 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001892 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001893
Michal Vasko7f1ee932018-10-11 09:41:42 +02001894 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001895 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001896 if (cur_session->status != NC_STATUS_RUNNING) {
1897 ret |= NC_PSPOLL_SESSION_TERM;
1898 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1899 ret |= NC_PSPOLL_SESSION_ERROR;
1900 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001901 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001902 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001903 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001904 }
Michal Vasko428087d2016-01-14 16:04:28 +01001905 }
Michal Vasko77367452021-02-16 16:32:18 +01001906 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001907
Michal Vasko131120a2018-05-29 15:44:02 +02001908 /* SESSION RPC UNLOCK */
1909 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001910 }
1911
Michal Vasko48a63ed2016-03-01 09:48:21 +01001912 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001913}
1914
Michal Vaskod09eae62016-02-01 10:32:52 +01001915API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001916nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001917{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001918 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001919 uint16_t i;
1920 struct nc_session *session;
1921
Michal Vasko9a25e932016-02-01 10:36:42 +01001922 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001923 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001924 return;
1925 }
1926
Michal Vasko48a63ed2016-03-01 09:48:21 +01001927 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001928 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001929 return;
1930 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001931
Michal Vasko48a63ed2016-03-01 09:48:21 +01001932 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001933 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001934 nc_session_free(ps->sessions[i]->session, data_free);
1935 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001936 }
1937 free(ps->sessions);
1938 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001939 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001940 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001941 } else {
1942 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001943 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1944 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001945 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001946 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001947 continue;
1948 }
1949
1950 ++i;
1951 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001952 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001953
1954 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001955 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001956}
1957
romanfb3f7cf2023-11-30 16:10:09 +01001958int
1959nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1960{
1961 int sock = -1, set_addr, ret = 0;
1962
1963 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1964
1965 if (address) {
1966 set_addr = 1;
1967 } else {
1968 set_addr = 0;
1969 }
1970
1971 if (set_addr) {
1972 port = bind->port;
1973 } else {
1974 address = bind->address;
1975 }
1976
1977 /* we have all the information we need to create a listening socket */
1978 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1979 /* create new socket, close the old one */
1980 if (endpt->ti == NC_TI_UNIX) {
1981 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1982 } else {
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02001983 sock = nc_sock_listen_inet(address, port);
romanfb3f7cf2023-11-30 16:10:09 +01001984 }
1985
1986 if (sock == -1) {
1987 ret = 1;
1988 goto cleanup;
1989 }
1990
1991 if (bind->sock > -1) {
1992 close(bind->sock);
1993 }
1994 bind->sock = sock;
1995 }
1996
1997 if (sock > -1) {
1998 switch (endpt->ti) {
1999 case NC_TI_UNIX:
2000 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2001 break;
2002#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02002003 case NC_TI_SSH:
romanfb3f7cf2023-11-30 16:10:09 +01002004 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2005 break;
roman506354a2024-04-11 09:37:22 +02002006 case NC_TI_TLS:
romanfb3f7cf2023-11-30 16:10:09 +01002007 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2008 break;
2009#endif /* NC_ENABLED_SSH_TLS */
2010 default:
2011 ERRINT;
2012 ret = 1;
2013 break;
2014 }
2015 }
2016
2017cleanup:
2018 return ret;
2019}
2020
Michal Vasko29f2f022024-03-13 09:06:48 +01002021#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
2022
Michal Vasko6f865982023-11-21 12:10:42 +01002023/**
2024 * @brief Get UID of the owner of a socket.
2025 *
2026 * @param[in] sock Socket to analyze.
2027 * @param[out] uid Socket owner UID.
2028 * @return 0 on success,
2029 * @return -1 on error.
2030 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002031static int
apropp-molex4e903c32020-04-20 03:06:58 -04002032nc_get_uid(int sock, uid_t *uid)
2033{
Michal Vasko6f865982023-11-21 12:10:42 +01002034 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002035
Michal Vaskod3910912020-04-20 09:12:49 +02002036#ifdef SO_PEERCRED
2037 struct ucred ucred;
2038 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002039
Michal Vaskod3910912020-04-20 09:12:49 +02002040 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002041 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2042 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002043 *uid = ucred.uid;
2044 }
2045#else
Michal Vasko6f865982023-11-21 12:10:42 +01002046 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002047#endif
2048
Michal Vasko6f865982023-11-21 12:10:42 +01002049 if (r < 0) {
2050 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002051 return -1;
2052 }
apropp-molex4e903c32020-04-20 03:06:58 -04002053 return 0;
2054}
2055
Michal Vasko29f2f022024-03-13 09:06:48 +01002056#endif
2057
apropp-molex4e903c32020-04-20 03:06:58 -04002058static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002059nc_accept_unix(struct nc_session *session, int sock)
2060{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002061#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002062 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002063 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002064
Michal Vasko5f352c52019-07-10 16:12:06 +02002065 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002066 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002067 char *buf = NULL;
2068 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002069
Michal Vaskod3910912020-04-20 09:12:49 +02002070 if (nc_get_uid(sock, &uid)) {
2071 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002072 return -1;
2073 }
2074
romanf6e32012023-04-24 15:51:26 +02002075 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002076 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002077 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002078 close(sock);
2079 return -1;
2080 }
2081
2082 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002083 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002084 if (username == NULL) {
2085 ERRMEM;
2086 close(sock);
2087 return -1;
2088 }
Michal Vasko93224072021-11-09 12:14:28 +01002089 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002090
2091 session->ti.unixsock.sock = sock;
2092
2093 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002094#else
Michal Vasko29f2f022024-03-13 09:06:48 +01002095 (void)session;
2096 (void)sock;
2097
Claus Klein22091912020-01-20 13:45:47 +01002098 return -1;
2099#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002100}
2101
Michal Vaskoe2713da2016-08-22 16:06:40 +02002102API int
romanfb3f7cf2023-11-30 16:10:09 +01002103nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2104{
2105 int ret = 0;
2106 void *tmp;
2107 uint16_t i;
2108
2109 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2110
2111 /* CONFIG LOCK */
2112 pthread_rwlock_wrlock(&server_opts.config_lock);
2113
2114 /* check name uniqueness */
2115 for (i = 0; i < server_opts.endpt_count; i++) {
2116 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2117 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2118 ret = 1;
2119 goto cleanup;
2120 }
2121 }
2122
2123 /* alloc a new endpoint */
2124 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2125 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2126 server_opts.endpts = tmp;
2127 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2128
2129 /* alloc a new bind */
2130 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2131 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2132 server_opts.binds = tmp;
2133 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2134 server_opts.binds[server_opts.endpt_count].sock = -1;
2135 server_opts.endpt_count++;
2136
2137 /* set name and ti */
2138 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2139 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2140 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2141
2142 /* set the bind data */
2143 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2144 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2145
2146 /* alloc unix opts */
2147 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2148 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2149
2150 /* set the opts data */
2151 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2152 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2153 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2154 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2155 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2156
2157 /* start listening */
2158 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2159 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2160 if (ret) {
2161 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2162 goto cleanup;
2163 }
2164
2165cleanup:
2166 /* CONFIG UNLOCK */
2167 pthread_rwlock_unlock(&server_opts.config_lock);
2168 return ret;
2169}
2170
2171static void
2172nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2173{
2174 if (bind->sock > -1) {
2175 close(bind->sock);
2176 }
2177
2178 unlink(bind->address);
2179 free(bind->address);
2180 free(opts->address);
2181
2182 free(opts);
2183}
2184
2185void
2186_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2187{
2188 free(endpt->name);
2189 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2190
2191 server_opts.endpt_count--;
2192 if (!server_opts.endpt_count) {
2193 free(server_opts.endpts);
2194 free(server_opts.binds);
2195 server_opts.endpts = NULL;
2196 server_opts.binds = NULL;
2197 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2198 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2199 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2200 }
2201}
2202
2203API void
2204nc_server_del_endpt_unix_socket(const char *endpt_name)
2205{
2206 uint16_t i;
2207 struct nc_endpt *endpt = NULL;
2208 struct nc_bind *bind;
2209
romanb6ad37a2023-12-07 13:08:46 +01002210 NC_CHECK_ARG_RET(NULL, endpt_name, );
2211
romanfb3f7cf2023-11-30 16:10:09 +01002212 /* CONFIG LOCK */
2213 pthread_rwlock_wrlock(&server_opts.config_lock);
2214
romanfb3f7cf2023-11-30 16:10:09 +01002215 for (i = 0; i < server_opts.endpt_count; i++) {
2216 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2217 endpt = &server_opts.endpts[i];
2218 bind = &server_opts.binds[i];
2219 break;
2220 }
2221 }
2222 if (!endpt) {
2223 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2224 goto end;
2225 }
2226 if (endpt->ti != NC_TI_UNIX) {
2227 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2228 goto end;
2229 }
2230
2231 _nc_server_del_endpt_unix_socket(endpt, bind);
2232
2233end:
2234 /* CONFIG UNLOCK */
2235 pthread_rwlock_unlock(&server_opts.config_lock);
2236}
2237
2238API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002239nc_server_endpt_count(void)
2240{
2241 return server_opts.endpt_count;
2242}
2243
Michal Vasko71090fc2016-05-24 16:37:28 +02002244API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002245nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002246{
Michal Vasko71090fc2016-05-24 16:37:28 +02002247 NC_MSG_TYPE msgtype;
Michal Vasko87b42562024-05-03 12:02:01 +02002248 int sock = -1, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002249 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002250 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002251 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002252
romanf578cd52023-10-19 09:47:40 +02002253 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002254
romand82caf12024-03-05 14:21:39 +01002255 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
2256
2257 *session = NULL;
2258
Michal Vasko93224072021-11-09 12:14:28 +01002259 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002260 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002261
romanf578cd52023-10-19 09:47:40 +02002262 /* CONFIG LOCK */
2263 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002264
2265 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002266 ERR(NULL, "No endpoints to accept sessions on.");
Michal Vasko87b42562024-05-03 12:02:01 +02002267 msgtype = NC_MSG_ERROR;
2268 goto cleanup;
Michal Vasko51e514d2016-02-02 15:51:52 +01002269 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002270
romanf578cd52023-10-19 09:47:40 +02002271 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 +01002272 if (ret < 1) {
Michal Vasko87b42562024-05-03 12:02:01 +02002273 msgtype = (!ret ? NC_MSG_WOULDBLOCK : NC_MSG_ERROR);
2274 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002275 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002276 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002277
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02002278 /* configure keepalives */
2279 if (nc_sock_configure_ka(sock, &server_opts.endpts[bind_idx].ka)) {
2280 msgtype = NC_MSG_ERROR;
2281 goto cleanup;
2282 }
2283
Michal Vasko131120a2018-05-29 15:44:02 +02002284 *session = nc_new_session(NC_SERVER, 0);
Michal Vasko87b42562024-05-03 12:02:01 +02002285 NC_CHECK_ERRMEM_GOTO(!(*session), msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002286 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002287 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002288 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002289 (*session)->host = host;
Michal Vasko87b42562024-05-03 12:02:01 +02002290 host = NULL;
Michal Vasko1a38c862016-01-15 15:50:07 +01002291 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002292
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002293 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002294#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02002295 if (server_opts.endpts[bind_idx].ti == NC_TI_SSH) {
romanf578cd52023-10-19 09:47:40 +02002296 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002297 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002298 if (ret < 0) {
2299 msgtype = NC_MSG_ERROR;
2300 goto cleanup;
2301 } else if (!ret) {
2302 msgtype = NC_MSG_WOULDBLOCK;
2303 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002304 }
roman506354a2024-04-11 09:37:22 +02002305 } else if (server_opts.endpts[bind_idx].ti == NC_TI_TLS) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002306 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002307 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002308 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002309 if (ret < 0) {
2310 msgtype = NC_MSG_ERROR;
2311 goto cleanup;
2312 } else if (!ret) {
2313 msgtype = NC_MSG_WOULDBLOCK;
2314 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002315 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002316 } else
romanf578cd52023-10-19 09:47:40 +02002317#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002318 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2319 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2320 ret = nc_accept_unix(*session, sock);
Michal Vasko87b42562024-05-03 12:02:01 +02002321 sock = -1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002322 if (ret < 0) {
2323 msgtype = NC_MSG_ERROR;
2324 goto cleanup;
2325 }
2326 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002327 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02002328 msgtype = NC_MSG_ERROR;
2329 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002330 }
2331
Michal Vasko2cc4c682016-03-01 09:16:48 +01002332 (*session)->data = NULL;
2333
romanf578cd52023-10-19 09:47:40 +02002334 /* CONFIG UNLOCK */
2335 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002336
Michal Vaskob48aa812016-01-18 14:13:09 +01002337 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002338 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002339
Michal Vasko9e036d52016-01-08 10:49:26 +01002340 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002341 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002342 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002343 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002344 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002345 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002346 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002347
Michal Vaskod8a74192023-02-06 15:51:50 +01002348 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002349 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002350 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002351 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002352 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002353
Michal Vasko71090fc2016-05-24 16:37:28 +02002354 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002355
Michal Vasko71090fc2016-05-24 16:37:28 +02002356cleanup:
romanf578cd52023-10-19 09:47:40 +02002357 /* CONFIG UNLOCK */
2358 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002359
Michal Vasko87b42562024-05-03 12:02:01 +02002360 free(host);
2361 if (sock > -1) {
2362 close(sock);
2363 }
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002364 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002365 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002366 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002367}
2368
romanf578cd52023-10-19 09:47:40 +02002369#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002370
2371API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002372nc_server_ch_is_client(const char *name)
2373{
2374 uint16_t i;
2375 int found = 0;
2376
2377 if (!name) {
2378 return found;
2379 }
2380
2381 /* READ LOCK */
2382 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2383
2384 /* check name uniqueness */
2385 for (i = 0; i < server_opts.ch_client_count; ++i) {
2386 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2387 found = 1;
2388 break;
2389 }
2390 }
2391
2392 /* UNLOCK */
2393 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2394
2395 return found;
2396}
2397
2398API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002399nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2400{
2401 uint16_t i;
2402 struct nc_ch_client *client = NULL;
2403 int found = 0;
2404
2405 if (!client_name || !endpt_name) {
2406 return found;
2407 }
2408
2409 /* READ LOCK */
2410 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2411
2412 for (i = 0; i < server_opts.ch_client_count; ++i) {
2413 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2414 client = &server_opts.ch_clients[i];
2415 break;
2416 }
2417 }
2418
2419 if (!client) {
2420 goto cleanup;
2421 }
2422
2423 for (i = 0; i < client->ch_endpt_count; ++i) {
2424 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2425 found = 1;
2426 break;
2427 }
2428 }
2429
2430cleanup:
2431 /* UNLOCK */
2432 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2433 return found;
2434}
2435
Michal Vasko056f53c2022-10-21 13:38:15 +02002436/**
2437 * @brief Create a connection for an endpoint.
2438 *
2439 * Client lock is expected to be held.
2440 *
2441 * @param[in] endpt Endpoint to use.
2442 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2443 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2444 * @param[in] ctx_cb_data Context callbacks data.
2445 * @param[out] session Created NC session.
2446 * @return NC_MSG values.
2447 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002448static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002449nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2450 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 +01002451{
Michal Vasko71090fc2016-05-24 16:37:28 +02002452 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002453 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002454 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002455 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002456 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002457
Michal Vasko056f53c2022-10-21 13:38:15 +02002458 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 +01002459 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002460 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002461 }
2462
Michal Vasko93224072021-11-09 12:14:28 +01002463 /* acquire context */
2464 ctx = acquire_ctx_cb(ctx_cb_data);
2465 if (!ctx) {
2466 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2467 close(sock);
2468 free(ip_host);
2469 return NC_MSG_ERROR;
2470 }
2471
romanf578cd52023-10-19 09:47:40 +02002472 /* init ctx as needed */
2473 nc_server_init_cb_ctx(ctx);
2474
Michal Vasko93224072021-11-09 12:14:28 +01002475 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002476 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002477 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002478 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002479 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002480 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002481 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002482 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002483
Michal Vaskob05053d2016-01-22 16:12:06 +01002484 /* sock gets assigned to session or closed */
roman506354a2024-04-11 09:37:22 +02002485 if (endpt->ti == NC_TI_SSH) {
romanf578cd52023-10-19 09:47:40 +02002486 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002487 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002488
Michal Vasko71090fc2016-05-24 16:37:28 +02002489 if (ret < 0) {
2490 msgtype = NC_MSG_ERROR;
2491 goto fail;
2492 } else if (!ret) {
2493 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002494 goto fail;
2495 }
roman506354a2024-04-11 09:37:22 +02002496 } else if (endpt->ti == NC_TI_TLS) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002497 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002498 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002499 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002500
Michal Vasko71090fc2016-05-24 16:37:28 +02002501 if (ret < 0) {
2502 msgtype = NC_MSG_ERROR;
2503 goto fail;
2504 } else if (!ret) {
2505 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002506 goto fail;
2507 }
roman423cc0d2023-11-24 11:29:47 +01002508 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002509 ERRINT;
2510 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002511 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002512 goto fail;
2513 }
2514
2515 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002516 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002517
2518 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002519 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002520 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002521 goto fail;
2522 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002523
Michal Vaskod8a74192023-02-06 15:51:50 +01002524 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002525 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002526 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002527 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002528 (*session)->status = NC_STATUS_RUNNING;
2529
Michal Vasko71090fc2016-05-24 16:37:28 +02002530 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002531
2532fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002533 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002534 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002535 if (ctx) {
2536 release_ctx_cb(ctx_cb_data);
2537 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002538 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002539}
2540
Michal Vasko6f865982023-11-21 12:10:42 +01002541/**
2542 * @brief Wait for any event after a NC session was established on a CH client.
2543 *
Michal Vasko6f865982023-11-21 12:10:42 +01002544 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002545 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002546 * @return 0 if session was terminated normally,
2547 * @return 1 if the CH client was removed,
2548 * @return -1 on error.
2549 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002550static int
roman8341e8b2023-11-23 16:12:42 +01002551nc_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 +02002552{
Michal Vasko6f865982023-11-21 12:10:42 +01002553 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002554 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002555 struct timespec ts;
2556 struct nc_ch_client *client;
2557
Michal Vasko2e6defd2016-10-07 15:48:15 +02002558 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002559 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002560
Michal Vaskofeccb312022-03-24 15:24:59 +01002561 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002562
Michal Vasko2e6defd2016-10-07 15:48:15 +02002563 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002564 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002565 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002566 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002567
2568 /* CH UNLOCK */
2569 pthread_mutex_unlock(&session->opts.server.ch_lock);
2570
Michal Vasko77d56d72022-09-07 10:30:48 +02002571 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002572 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002573 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002574 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002575 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002576
2577 do {
romanf578cd52023-10-19 09:47:40 +02002578 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002579
Michal Vasko0db3db52021-03-03 10:45:42 +01002580 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002581 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 +01002582 if (!r) {
2583 /* we were woken up, something probably happened */
2584 if (session->status != NC_STATUS_RUNNING) {
2585 break;
2586 }
2587 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002588 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002589 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002590 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002591 }
2592
Michal Vasko2e6defd2016-10-07 15:48:15 +02002593 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002594
Michal Vasko2e6defd2016-10-07 15:48:15 +02002595 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002596 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002597 if (!client) {
2598 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002599 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002600 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002601 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002602 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002603 }
2604
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002605 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002606 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002607 } else {
2608 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002609 }
2610
Michal Vaskod8a74192023-02-06 15:51:50 +01002611 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002612 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 +02002613 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002614 session->status = NC_STATUS_INVALID;
2615 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2616 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002617
2618 /* UNLOCK */
2619 nc_server_ch_client_unlock(client);
2620
2621 } while (session->status == NC_STATUS_RUNNING);
2622
Michal Vaskofeccb312022-03-24 15:24:59 +01002623 /* signal to nc_session_free() that CH thread is terminating */
2624 session->flags &= ~NC_SESSION_CH_THREAD;
2625 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002626
Michal Vasko27377422018-03-15 08:59:35 +01002627 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002628 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002629
Michal Vasko6f865982023-11-21 12:10:42 +01002630 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002631}
2632
romanf578cd52023-10-19 09:47:40 +02002633/**
2634 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2635 *
2636 * @param[in] session An established session.
2637 * @param[in] data Call Home thread's data.
2638 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2639 *
2640 * @return 0 if the thread should stop running, 1 if it should continue.
2641 */
2642static int
2643nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2644{
2645 struct timespec ts;
2646 int ret = 0, thread_running;
2647
2648 /* COND LOCK */
2649 pthread_mutex_lock(&data->cond_lock);
2650 /* get reconnect timeout in ms */
2651 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2652 while (!ret && data->thread_running) {
2653 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2654 }
2655
2656 thread_running = data->thread_running;
2657 /* COND UNLOCK */
2658 pthread_mutex_unlock(&data->cond_lock);
2659
2660 if (!thread_running) {
2661 /* thread is terminating */
2662 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2663 ret = 0;
2664 } else if (ret == ETIMEDOUT) {
2665 /* time to reconnect */
2666 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2667 ret = 1;
2668 } else if (ret) {
2669 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2670 ret = 0;
2671 }
2672
2673 return ret;
2674}
2675
2676/**
2677 * @brief Checks if a Call Home thread should terminate.
2678 *
2679 * Checks the shared boolean variable thread_running. This should be done everytime
2680 * before entering a critical section.
2681 *
2682 * @param[in] data Call Home thread's data.
2683 *
2684 * @return 0 if the thread should stop running, -1 if it can continue.
2685 */
2686static int
2687nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2688{
2689 int ret = -1;
2690
2691 /* COND LOCK */
2692 pthread_mutex_lock(&data->cond_lock);
2693 if (!data->thread_running) {
2694 /* thread should stop running */
2695 ret = 0;
2696 }
2697 /* COND UNLOCK */
2698 pthread_mutex_unlock(&data->cond_lock);
2699
2700 return ret;
2701}
2702
Michal Vasko6f865982023-11-21 12:10:42 +01002703/**
2704 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2705 *
2706 * @param[in] name Name of the CH client.
2707 * @return Pointer to the CH client.
2708 */
2709static struct nc_ch_client *
2710nc_server_ch_client_with_endpt_lock(const char *name)
2711{
2712 struct nc_ch_client *client;
2713
2714 while (1) {
2715 /* LOCK */
2716 client = nc_server_ch_client_lock(name);
2717 if (!client) {
2718 return NULL;
2719 }
2720 if (client->ch_endpt_count) {
2721 return client;
2722 }
2723 /* no endpoints defined yet */
2724
2725 /* UNLOCK */
2726 nc_server_ch_client_unlock(client);
2727
2728 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2729 }
2730
2731 return NULL;
2732}
2733
2734/**
2735 * @brief Call Home client management thread.
2736 *
2737 * @param[in] arg CH client thread argument.
2738 * @return NULL.
2739 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002740static void *
2741nc_ch_client_thread(void *arg)
2742{
Michal Vasko6f865982023-11-21 12:10:42 +01002743 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002744 NC_MSG_TYPE msgtype;
2745 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002746 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002747 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002748 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002749 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002750 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002751 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002752
2753 /* LOCK */
2754 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002755 if (!client) {
2756 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2757 goto cleanup;
2758 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002759
2760 cur_endpt = &client->ch_endpts[0];
2761 cur_endpt_name = strdup(cur_endpt->name);
2762
Michal Vasko6f865982023-11-21 12:10:42 +01002763 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002764 if (!cur_attempts) {
2765 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2766 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002767
Michal Vasko6f865982023-11-21 12:10:42 +01002768 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 +02002769 if (msgtype == NC_MSG_HELLO) {
2770 /* UNLOCK */
2771 nc_server_ch_client_unlock(client);
2772
romanf578cd52023-10-19 09:47:40 +02002773 if (!nc_server_ch_client_thread_is_running(data)) {
2774 /* thread should stop running */
2775 goto cleanup;
2776 }
2777
2778 /* run while the session is established */
2779 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002780 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002781 goto cleanup;
2782 }
roman8341e8b2023-11-23 16:12:42 +01002783 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002784
roman8341e8b2023-11-23 16:12:42 +01002785 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002786 if (!nc_server_ch_client_thread_is_running(data)) {
2787 /* thread should stop running */
2788 goto cleanup;
2789 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002790
2791 /* LOCK */
2792 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002793 if (!client) {
2794 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2795 goto cleanup;
2796 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002797
2798 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002799 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002800 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002801 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002802 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002803 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002804 } else {
2805 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002806 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002807 }
2808
Michal Vasko2e6defd2016-10-07 15:48:15 +02002809 /* UNLOCK */
2810 nc_server_ch_client_unlock(client);
2811
romanf578cd52023-10-19 09:47:40 +02002812 /* wait for the timeout to elapse, so we can try to reconnect */
2813 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2814 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2815 goto cleanup;
2816 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002817
2818 /* LOCK */
2819 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002820 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002821 }
2822
2823 /* set next endpoint to try */
2824 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002825 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002826 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002827 /* we keep the current one but due to unlock/lock we have to find it again */
2828 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2829 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2830 break;
2831 }
2832 }
2833 if (next_endpt_index >= client->ch_endpt_count) {
2834 /* endpoint was removed, start with the first one */
2835 next_endpt_index = 0;
2836 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002837 } else {
2838 /* just get a random index */
2839 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002840 }
2841
Michal Vasko2e6defd2016-10-07 15:48:15 +02002842 } else {
romanf578cd52023-10-19 09:47:40 +02002843 /* session was not created, wait a little bit and try again */
2844 max_wait = client->max_wait;
2845
Michal Vasko6bb116b2016-10-26 13:53:46 +02002846 /* UNLOCK */
2847 nc_server_ch_client_unlock(client);
2848
romanf578cd52023-10-19 09:47:40 +02002849 /* wait for max_wait seconds */
2850 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2851 /* thread should stop running */
2852 goto cleanup;
2853 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002854
Michal Vasko6bb116b2016-10-26 13:53:46 +02002855 /* LOCK */
2856 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002857 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002858
Michal Vasko2e6defd2016-10-07 15:48:15 +02002859 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002860
2861 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002862 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2863 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002864 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002865 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002866 }
2867
Peter Feiged05f2252018-09-03 08:09:47 +00002868 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002869 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002870 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002871 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002872 cur_attempts = 0;
2873 } else if (cur_attempts == client->max_attempts) {
2874 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002875 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002876 data->client_name, cur_endpt_name, client->max_attempts);
2877
2878 /* clear a pending socket, if any */
2879 cur_endpt = &client->ch_endpts[next_endpt_index];
2880 if (cur_endpt->sock_pending > -1) {
2881 close(cur_endpt->sock_pending);
2882 cur_endpt->sock_pending = -1;
2883 }
2884
Peter Feiged05f2252018-09-03 08:09:47 +00002885 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002886 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002887 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002888 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002889 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002890 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002891 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002892 cur_attempts = 0;
2893 } /* else we keep the current one */
2894 }
Peter Feiged05f2252018-09-03 08:09:47 +00002895
2896 cur_endpt = &client->ch_endpts[next_endpt_index];
2897 free(cur_endpt_name);
2898 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002899 }
Michal Vasko6f865982023-11-21 12:10:42 +01002900
romanf578cd52023-10-19 09:47:40 +02002901 /* UNLOCK if we break out of the loop */
2902 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002903
2904cleanup:
romanf578cd52023-10-19 09:47:40 +02002905 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002906 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002907 free(data->client_name);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002908 pthread_mutex_lock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002909 pthread_cond_destroy(&data->cond);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002910 pthread_mutex_unlock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002911 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002912 free(data);
2913 return NULL;
2914}
2915
2916API int
Michal Vasko93224072021-11-09 12:14:28 +01002917nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002918 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2919 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002920{
Michal Vasko6f865982023-11-21 12:10:42 +01002921 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002922 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002923 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002924 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002925
romanf578cd52023-10-19 09:47:40 +02002926 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2927
romand82caf12024-03-05 14:21:39 +01002928 NC_CHECK_SRV_INIT_RET(-1);
2929
Michal Vasko6f865982023-11-21 12:10:42 +01002930 /* LOCK */
2931 ch_client = nc_server_ch_client_lock(client_name);
2932 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002933 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002934 return -1;
2935 }
2936
Michal Vasko6f865982023-11-21 12:10:42 +01002937 /* create the thread argument */
2938 arg = calloc(1, sizeof *arg);
2939 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002940 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002941 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002942 arg->acquire_ctx_cb = acquire_ctx_cb;
2943 arg->release_ctx_cb = release_ctx_cb;
2944 arg->ctx_cb_data = ctx_cb_data;
2945 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002946 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002947 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002948 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002949
Michal Vasko6f865982023-11-21 12:10:42 +01002950 /* creating the thread */
2951 arg->thread_running = 1;
2952 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2953 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2954 rc = -1;
2955 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002956 }
Michal Vasko6f865982023-11-21 12:10:42 +01002957
Michal Vasko2e6defd2016-10-07 15:48:15 +02002958 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002959 ch_client->tid = tid;
2960 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002961 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002962
Michal Vasko6f865982023-11-21 12:10:42 +01002963cleanup:
2964 /* UNLOCK */
2965 nc_server_ch_client_unlock(ch_client);
2966
2967 if (arg) {
2968 free(arg->client_name);
2969 free(arg);
2970 }
2971 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002972}
2973
romanf578cd52023-10-19 09:47:40 +02002974#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002975
roman44efa322023-11-03 13:57:25 +01002976API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002977nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002978{
roman44efa322023-11-03 13:57:25 +01002979 struct timespec fail = {0};
2980
2981 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002982
2983 if (session->side != NC_SERVER) {
2984 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002985 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002986 }
2987
Michal Vasko2e6defd2016-10-07 15:48:15 +02002988 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002989}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002990
2991API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002992nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002993{
2994 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002995 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002996 return;
2997 }
2998
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002999 /* NTF STATUS LOCK */
3000 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3001
Michal Vasko71dbd772021-03-23 14:08:37 +01003002 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003003
3004 /* NTF STATUS UNLOCK */
3005 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01003006}
3007
3008API void
3009nc_session_dec_notif_status(struct nc_session *session)
3010{
3011 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003012 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01003013 return;
3014 }
3015
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003016 /* NTF STATUS LOCK */
3017 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3018
Michal Vasko71dbd772021-03-23 14:08:37 +01003019 if (session->opts.server.ntf_status) {
3020 --session->opts.server.ntf_status;
3021 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003022
3023 /* NTF STATUS UNLOCK */
3024 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003025}
3026
3027API int
3028nc_session_get_notif_status(const struct nc_session *session)
3029{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003030 uint32_t ntf_status;
3031
Michal Vasko3486a7c2017-03-03 13:28:07 +01003032 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003033 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003034 return 0;
3035 }
3036
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003037 /* NTF STATUS LOCK */
3038 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3039
3040 ntf_status = session->opts.server.ntf_status;
3041
3042 /* NTF STATUS UNLOCK */
3043 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3044
3045 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003046}