blob: 4f4633e7123276c780333d2c90bd12450e6f13c5 [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"
roman3962bc82024-07-09 15:06:45 +020041#include "server_config.h"
romanf578cd52023-10-19 09:47:40 +020042#include "server_config_p.h"
43#include "session.h"
44#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010045#include "session_server.h"
Michal Vasko0bdf70b2019-06-24 19:20:20 +020046#include "session_server_ch.h"
roman9225e912024-04-05 12:33:09 +020047#include "session_wrapper.h"
48
49#ifdef NC_ENABLED_SSH_TLS
50#include <curl/curl.h>
51#include <libssh/libssh.h>
52#endif
Michal Vasko086311b2016-01-08 09:53:11 +010053
Michal Vaskob48aa812016-01-18 14:13:09 +010054struct nc_server_opts server_opts = {
romanf578cd52023-10-19 09:47:40 +020055 .config_lock = PTHREAD_RWLOCK_INITIALIZER,
56 .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER,
Michal Vaskocf898172024-01-15 15:04:28 +010057 .idle_timeout = 180, /**< default idle timeout (not in config for UNIX socket) */
Michal Vaskob48aa812016-01-18 14:13:09 +010058};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010059
fanchanghu966f2de2016-07-21 02:28:57 -040060static nc_rpc_clb global_rpc_clb = NULL;
61
roman423cc0d2023-11-24 11:29:47 +010062#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010063/**
64 * @brief Lock CH client structures for reading and lock the specific client.
65 *
66 * @param[in] name Name of the CH client.
67 * @return CH client, NULL if not found.
68 */
69static struct nc_ch_client *
70nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010071{
72 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020073 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020074
Michal Vasko6f865982023-11-21 12:10:42 +010075 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020076
Michal Vasko2e6defd2016-10-07 15:48:15 +020077 /* READ LOCK */
78 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
79
80 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020081 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020082 client = &server_opts.ch_clients[i];
83 break;
84 }
85 }
86
87 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020088 /* READ UNLOCK */
89 pthread_rwlock_unlock(&server_opts.ch_client_lock);
90 } else {
91 /* CH CLIENT LOCK */
92 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020093 }
94
Michal Vasko6f865982023-11-21 12:10:42 +010095 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020096}
97
Michal Vasko6f865982023-11-21 12:10:42 +010098/**
99 * @brief Unlock CH client strcutures and the specific client.
100 *
101 * @param[in] endpt Locked CH client structure.
102 */
103static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200104nc_server_ch_client_unlock(struct nc_ch_client *client)
105{
106 /* CH CLIENT UNLOCK */
107 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100108
109 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200110 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100111}
Michal Vasko086311b2016-01-08 09:53:11 +0100112
roman423cc0d2023-11-24 11:29:47 +0100113#endif /* NC_ENABLED_SSH_TLS */
114
roman78df0fa2023-11-02 10:33:57 +0100115int
116nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
117{
118 uint16_t i;
119
120 for (i = 0; i < server_opts.endpt_count; i++) {
121 if (!strcmp(name, server_opts.endpts[i].name)) {
122 *endpt = &server_opts.endpts[i];
123 return 0;
124 }
125 }
126
127 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
128 return 1;
129}
130
Michal Vasko1a38c862016-01-15 15:50:07 +0100131API void
132nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
133{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200134 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200135 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200136 return;
137 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200138 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100139 return;
140 }
141
Michal Vasko142cfea2017-08-07 10:12:11 +0200142 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
143 session->killed_by = 0;
144 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100145 session->term_reason = reason;
146}
147
Michal Vasko142cfea2017-08-07 10:12:11 +0200148API void
149nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
150{
151 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200152 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200153 return;
154 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200155 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200156 return;
157 }
158
159 session->killed_by = sid;
160}
161
162API void
163nc_session_set_status(struct nc_session *session, NC_STATUS status)
164{
165 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200166 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200167 return;
168 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200169 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200170 return;
171 }
172
173 session->status = status;
174}
175
romanf578cd52023-10-19 09:47:40 +0200176API int
177nc_server_init_ctx(struct ly_ctx **ctx)
178{
179 int new_ctx = 0, i, ret = 0;
180 struct lys_module *module;
181 /* all features */
182 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
183 /* all features (module has no features) */
184 const char *ietf_netconf_monitoring_features[] = {NULL};
185
186 NC_CHECK_ARG_RET(NULL, ctx, 1);
187
188 if (!*ctx) {
189 /* context not given, create a new one */
190 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
191 ERR(NULL, "Couldn't create new libyang context.\n");
192 ret = 1;
193 goto cleanup;
194 }
195 new_ctx = 1;
196 }
197
198 if (new_ctx) {
199 /* new context created, implement both modules */
200 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
201 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
202 ret = 1;
203 goto cleanup;
204 }
205
206 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
207 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
208 ret = 1;
209 goto cleanup;
210 }
211
212 goto cleanup;
213 }
214
215 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
216 if (module) {
217 /* ietf-netconf module is present, check features */
218 for (i = 0; ietf_netconf_features[i]; i++) {
219 if (lys_feature_value(module, ietf_netconf_features[i])) {
220 /* feature not found, enable all of them */
221 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
222 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
223 ret = 1;
224 goto cleanup;
225 }
226
227 break;
228 }
229 }
230 } else {
231 /* ietf-netconf module not found, add it */
232 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
233 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
234 ret = 1;
235 goto cleanup;
236 }
237 }
238
239 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
240 if (!module) {
241 /* ietf-netconf-monitoring module not found, add it */
242 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
243 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
244 ret = 1;
245 goto cleanup;
246 }
247 }
248
249cleanup:
250 if (new_ctx && ret) {
251 ly_ctx_destroy(*ctx);
252 *ctx = NULL;
253 }
254 return ret;
255}
256
roman96c27f92023-11-02 11:09:46 +0100257#ifdef NC_ENABLED_SSH_TLS
258
roman450c00b2023-11-02 10:31:45 +0100259API void
260nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
261 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
262 void *new_session_cb_data)
263{
264 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
265
266 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
267 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
268 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
269 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
270 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
271}
272
roman96c27f92023-11-02 11:09:46 +0100273#endif
274
Michal Vasko086311b2016-01-08 09:53:11 +0100275int
roman9ad4a8a2024-08-08 12:44:01 +0200276nc_sock_bind_inet(int sock, const char *address, uint16_t port, int is_ipv4)
277{
278 struct sockaddr_storage saddr;
279 struct sockaddr_in *saddr4;
280 struct sockaddr_in6 *saddr6;
281
282 memset(&saddr, 0, sizeof(struct sockaddr_storage));
283
284 if (is_ipv4) {
285 saddr4 = (struct sockaddr_in *)&saddr;
286
287 saddr4->sin_family = AF_INET;
288 saddr4->sin_port = htons(port);
289
290 /* determine the address */
291 if (!address) {
292 /* set the implicit default IPv4 address */
293 address = "0.0.0.0";
294 }
295 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
296 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
297 return -1;
298 }
299
300 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
301 ERR(NULL, "Could not bind %s:%" PRIu16 " (%s).", address, port, strerror(errno));
302 return -1;
303 }
304
305 } else {
306 saddr6 = (struct sockaddr_in6 *)&saddr;
307
308 saddr6->sin6_family = AF_INET6;
309 saddr6->sin6_port = htons(port);
310
311 /* determine the address */
312 if (!address) {
313 /* set the implicit default IPv6 address */
314 address = "::";
315 }
316 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
317 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
318 return -1;
319 }
320
321 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
322 ERR(NULL, "Could not bind [%s]:%" PRIu16 " (%s).", address, port, strerror(errno));
323 return -1;
324 }
325 }
326
327 return 0;
328}
329
330int
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200331nc_sock_listen_inet(const char *address, uint16_t port)
Michal Vasko086311b2016-01-08 09:53:11 +0100332{
Michal Vasko06c860d2018-07-09 16:08:52 +0200333 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100334 int is_ipv4, sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100335
Michal Vasko086311b2016-01-08 09:53:11 +0100336 if (!strchr(address, ':')) {
337 is_ipv4 = 1;
338 } else {
339 is_ipv4 = 0;
340 }
341
342 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
343 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200344 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100345 goto fail;
346 }
347
Michal Vaskobe52dc22018-10-17 09:28:17 +0200348 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200349 opt = 1;
350 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200351 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200352 goto fail;
353 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100354 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200355 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100356 goto fail;
357 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200358
roman9ad4a8a2024-08-08 12:44:01 +0200359 /* bind the socket */
360 if (nc_sock_bind_inet(sock, address, port, is_ipv4)) {
361 goto fail;
Michal Vasko086311b2016-01-08 09:53:11 +0100362 }
363
Michal Vaskofb89d772016-01-08 12:25:35 +0100364 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200365 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100366 goto fail;
367 }
Michal Vasko086311b2016-01-08 09:53:11 +0100368 return sock;
369
370fail:
371 if (sock > -1) {
372 close(sock);
373 }
374
375 return -1;
376}
377
Michal Vaskoc429a8e2024-01-15 15:04:57 +0100378/**
379 * @brief Create a listening socket (AF_UNIX).
380 *
381 * @param[in] opts The server options (unix permissions and address of the socket).
382 * @return Listening socket, -1 on error.
383 */
384static int
roman83683fb2023-02-24 09:15:23 +0100385nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200386{
387 struct sockaddr_un sun;
388 int sock = -1;
389
roman83683fb2023-02-24 09:15:23 +0100390 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
391 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 +0200392 goto fail;
393 }
394
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200395 sock = socket(AF_UNIX, SOCK_STREAM, 0);
396 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200397 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200398 goto fail;
399 }
400
401 memset(&sun, 0, sizeof(sun));
402 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100403 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200404
405 unlink(sun.sun_path);
406 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100407 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200408 goto fail;
409 }
410
411 if (opts->mode != (mode_t)-1) {
412 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200413 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200414 goto fail;
415 }
416 }
417
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200418 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200419 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200420 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200421 goto fail;
422 }
423 }
424
425 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100426 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200427 goto fail;
428 }
429
430 return sock;
431
432fail:
433 if (sock > -1) {
434 close(sock);
435 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200436 return -1;
437}
438
aPiecek90ff0242021-02-14 14:58:01 +0100439/**
440 * @brief Evaluate socket name for AF_UNIX socket.
441 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
442 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
443 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
444 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
445 * @return -1 in case of error. Parameter host is set to NULL.
446 */
447static int
448sock_host_unix(int acc_sock_fd, char **host)
449{
450 char *sun_path;
451 struct sockaddr_storage saddr;
452 socklen_t addr_len;
453
454 *host = NULL;
455 saddr.ss_family = AF_UNIX;
456 addr_len = sizeof(saddr);
457
458 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200459 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100460 return -1;
461 }
462
463 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
464 if (!sun_path) {
465 /* stream socket is unnamed */
466 return 0;
467 }
468
roman3a95bb22023-10-26 11:07:17 +0200469 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100470
471 return 0;
472}
473
474/**
475 * @brief Evaluate socket name and port number for AF_INET socket.
476 * @param[in] addr is pointing to structure filled by accept function which was successful.
477 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
478 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
479 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
480 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
481 */
482static int
483sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
484{
485 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200486 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100487
aPiecek3da9b342021-02-18 15:00:03 +0100488 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100489 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100490 free(*host);
491 *host = NULL;
492 return -1;
493 }
494
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200495 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100496
497 return 0;
498}
499
500/**
501 * @brief Evaluate socket name and port number for AF_INET6 socket.
502 * @param[in] addr is pointing to structure filled by accept function which was successful.
503 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
504 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
505 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
506 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
507 */
508static int
509sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
510{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200511 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200512 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100513
aPiecek3da9b342021-02-18 15:00:03 +0100514 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100515 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100516 free(*host);
517 *host = NULL;
518 return -1;
519 }
520
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200521 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100522
523 return 0;
524}
525
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200526int
Michal Vasko6f865982023-11-21 12:10:42 +0100527nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
Michal Vasko8b1740f2024-06-28 13:47:19 +0200528 uint16_t *port, uint16_t *idx, int *sock)
Michal Vasko086311b2016-01-08 09:53:11 +0100529{
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200530 uint16_t i, j, pfd_count, client_port;
531 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100532 struct pollfd *pfd;
533 struct sockaddr_storage saddr;
534 socklen_t saddr_len = sizeof(saddr);
Michal Vasko8b1740f2024-06-28 13:47:19 +0200535 int ret, client_sock, server_sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100536
537 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200538 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100539
romanf578cd52023-10-19 09:47:40 +0200540 /* LOCK */
541 pthread_mutex_lock(bind_lock);
542
Michal Vaskoac2f6182017-01-30 14:32:03 +0100543 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200544 if (binds[i].sock < 0) {
545 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200546 continue;
547 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200548 if (binds[i].pollin) {
549 binds[i].pollin = 0;
550 /* leftover pollin */
Michal Vasko8b1740f2024-06-28 13:47:19 +0200551 server_sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100552 break;
553 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100554 pfd[pfd_count].fd = binds[i].sock;
555 pfd[pfd_count].events = POLLIN;
556 pfd[pfd_count].revents = 0;
557
558 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100559 }
560
Michal Vasko8b1740f2024-06-28 13:47:19 +0200561 if (server_sock == -1) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200562 /* poll for a new connection */
Michal Vasko63b92d62024-04-29 10:04:56 +0200563 ret = nc_poll(pfd, pfd_count, timeout);
564 if (ret < 1) {
565 free(pfd);
Michal Vaskof54cd352017-02-22 13:42:02 +0100566
romanf578cd52023-10-19 09:47:40 +0200567 /* UNLOCK */
568 pthread_mutex_unlock(bind_lock);
Michal Vasko63b92d62024-04-29 10:04:56 +0200569
570 return ret;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200571 }
Michal Vasko086311b2016-01-08 09:53:11 +0100572
Michal Vaskoac2f6182017-01-30 14:32:03 +0100573 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
574 /* adjust i so that indices in binds and pfd always match */
575 while (binds[i].sock != pfd[j].fd) {
576 ++i;
577 }
578
579 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200580 --ret;
581
582 if (!ret) {
583 /* the last socket with an event, use it */
Michal Vasko8b1740f2024-06-28 13:47:19 +0200584 server_sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200585 break;
586 } else {
587 /* just remember the event for next time */
588 binds[i].pollin = 1;
589 }
590 }
Michal Vasko086311b2016-01-08 09:53:11 +0100591 }
592 }
593 free(pfd);
Michal Vasko8b1740f2024-06-28 13:47:19 +0200594 if (server_sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100595 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200596 /* UNLOCK */
597 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100598 return -1;
599 }
600
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200601 /* accept connection */
Michal Vasko8b1740f2024-06-28 13:47:19 +0200602 client_sock = accept(server_sock, (struct sockaddr *)&saddr, &saddr_len);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200603 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200604 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200605 /* UNLOCK */
606 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100607 return -1;
608 }
609
Michal Vasko0190bc32016-03-02 15:47:49 +0100610 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200611 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200612 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200613 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100614 }
615
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200616 /* learn information about the client end */
617 if (saddr.ss_family == AF_UNIX) {
618 if (sock_host_unix(client_sock, &client_address)) {
619 goto fail;
620 }
621 client_port = 0;
622 } else if (saddr.ss_family == AF_INET) {
623 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
624 goto fail;
625 }
626 } else if (saddr.ss_family == AF_INET6) {
627 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
628 goto fail;
629 }
630 } else {
631 ERR(NULL, "Source host of an unknown protocol family.");
632 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100633 }
Michal Vasko086311b2016-01-08 09:53:11 +0100634
aPiecek90ff0242021-02-14 14:58:01 +0100635 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200636 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100637 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200638 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 +0100639 }
640
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200641 if (host) {
642 *host = client_address;
643 } else {
644 free(client_address);
645 }
646 if (port) {
647 *port = client_port;
648 }
649 if (idx) {
650 *idx = i;
651 }
romanf578cd52023-10-19 09:47:40 +0200652 /* UNLOCK */
653 pthread_mutex_unlock(bind_lock);
Michal Vasko8b1740f2024-06-28 13:47:19 +0200654
655 *sock = client_sock;
656 return 1;
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200657
658fail:
659 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200660 /* UNLOCK */
661 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200662 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100663}
664
Michal Vasko238b6c12021-12-14 15:14:09 +0100665API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200666nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100667{
Michal Vasko77367452021-02-16 16:32:18 +0100668 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100669 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100670 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100671 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100672 const struct lysp_submodule *submodule = NULL;
673 struct lyd_node *child, *err, *data = NULL;
674 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100675
Michal Vasko77367452021-02-16 16:32:18 +0100676 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100677 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200678 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100679 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200680 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200681 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100682 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500683 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100684 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200685 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100686 }
687 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200688 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100689
Michal Vasko77367452021-02-16 16:32:18 +0100690 /* check revision */
691 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100692 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100693 nc_err_set_msg(err, "The requested version is not supported.", "en");
694 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100695 }
696
Michal Vasko77367452021-02-16 16:32:18 +0100697 if (revision) {
698 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100699 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100700 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100701 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100702 }
703 } else {
704 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100705 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100706 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100707 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100708 }
709 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100710 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100711 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200712 }
Michal Vasko77367452021-02-16 16:32:18 +0100713 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100714 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200715 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100716 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100717 }
718
719 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100720 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100721 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100722 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100723 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100724 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100725 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100726 nc_err_set_msg(err, "The requested format is not supported.", "en");
727 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100728 }
Michal Vasko77367452021-02-16 16:32:18 +0100729
730 /* print */
731 ly_out_new_memory(&model_data, 0, &out);
732 if (module) {
733 lys_print_module(out, module, outformat, 0, 0);
734 } else {
735 lys_print_submodule(out, submodule, outformat, 0, 0);
736 }
737 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200738 if (!model_data) {
739 ERRINT;
740 return NULL;
741 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100742
Michal Vasko9b1a9522021-03-15 16:24:26 +0100743 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100744 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100745 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100746 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200747 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100748 return NULL;
749 }
Michal Vasko58791da2024-02-26 13:52:59 +0100750 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 +0100751 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100752 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100753 lyd_free_tree(data);
754 return NULL;
755 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100756
Radek Krejci36dfdb32016-09-01 16:56:35 +0200757 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100758}
759
Michal Vasko238b6c12021-12-14 15:14:09 +0100760API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100761nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100762{
Michal Vasko428087d2016-01-14 16:04:28 +0100763 session->term_reason = NC_SESSION_TERM_CLOSED;
764 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100765}
766
Michal Vasko93224072021-11-09 12:14:28 +0100767/**
768 * @brief Initialize a context with default RPC callbacks if none are set.
769 *
770 * @param[in] ctx Context to initialize.
771 */
772static void
romanf578cd52023-10-19 09:47:40 +0200773nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100774{
Michal Vasko77367452021-02-16 16:32:18 +0100775 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100776
Michal Vasko238b6c12021-12-14 15:14:09 +0100777 if (global_rpc_clb) {
778 /* expect it to handle these RPCs as well */
779 return;
780 }
781
Michal Vasko05ba9df2016-01-13 14:40:27 +0100782 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100783 rpc = NULL;
784 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
785 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
786 }
Michal Vasko88639e92017-08-03 14:38:10 +0200787 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100788 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100789 }
790
Michal Vasko93224072021-11-09 12:14:28 +0100791 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100792 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200793 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100794 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100795 }
Michal Vasko93224072021-11-09 12:14:28 +0100796}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100797
Michal Vasko93224072021-11-09 12:14:28 +0100798API int
799nc_server_init(void)
800{
Michal Vasko29f2f022024-03-13 09:06:48 +0100801 pthread_rwlockattr_t *attr_p = NULL;
Michal Vasko93224072021-11-09 12:14:28 +0100802 int r;
803
romand82caf12024-03-05 14:21:39 +0100804 ATOMIC_STORE_RELAXED(server_opts.new_session_id, 1);
805 ATOMIC_STORE_RELAXED(server_opts.new_client_id, 1);
Michal Vaskob48aa812016-01-18 14:13:09 +0100806
Michal Vasko93224072021-11-09 12:14:28 +0100807#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
Michal Vasko29f2f022024-03-13 09:06:48 +0100808 pthread_rwlockattr_t attr;
809
Michal Vasko93224072021-11-09 12:14:28 +0100810 if ((r = pthread_rwlockattr_init(&attr))) {
811 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
812 goto error;
813 }
814 attr_p = &attr;
815 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
816 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
817 goto error;
818 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700819#endif
Michal Vasko93224072021-11-09 12:14:28 +0100820
romanf578cd52023-10-19 09:47:40 +0200821 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100822 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
823 goto error;
824 }
825 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
826 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
827 goto error;
828 }
829
830 if (attr_p) {
831 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000832 }
romanf578cd52023-10-19 09:47:40 +0200833
834#ifdef NC_ENABLED_SSH_TLS
835 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
836 ERR(NULL, "%s: failed to init CURL.", __func__);
837 goto error;
838 }
Michal Vasko5788c802024-05-03 16:14:40 +0200839
840 /* optional for dynamic library, mandatory for static */
841 if (ssh_init()) {
842 ERR(NULL, "%s: failed to init libssh.", __func__);
843 goto error;
844 }
romanf578cd52023-10-19 09:47:40 +0200845#endif
846
847 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
848 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
849 goto error;
850 }
851
roman3962bc82024-07-09 15:06:45 +0200852#ifdef NC_ENABLED_SSH_TLS
roman2c105342024-07-10 16:09:45 +0200853 if ((r = pthread_mutex_init(&server_opts.cert_exp_notif.lock, NULL))) {
roman3962bc82024-07-09 15:06:45 +0200854 ERR(NULL, "%s: failed to init certificate expiration notification thread lock(%s).", __func__, strerror(r));
855 goto error;
856 }
roman2c105342024-07-10 16:09:45 +0200857 if ((r = pthread_cond_init(&server_opts.cert_exp_notif.cond, NULL))) {
roman3962bc82024-07-09 15:06:45 +0200858 ERR(NULL, "%s: failed to init certificate expiration notification thread condition(%s).", __func__, strerror(r));
859 goto error;
860 }
861#endif
862
Michal Vasko086311b2016-01-08 09:53:11 +0100863 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100864
865error:
866 if (attr_p) {
867 pthread_rwlockattr_destroy(attr_p);
868 }
869 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100870}
871
Michal Vaskob48aa812016-01-18 14:13:09 +0100872API void
873nc_server_destroy(void)
874{
romana2ff4ef2024-01-19 14:41:46 +0100875 uint32_t i, endpt_count;
Radek Krejci658782b2016-12-04 22:04:55 +0100876
877 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100878 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100879 }
880 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200881 server_opts.capabilities = NULL;
882 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200883 if (server_opts.content_id_data && server_opts.content_id_data_free) {
884 server_opts.content_id_data_free(server_opts.content_id_data);
885 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200886
roman3962bc82024-07-09 15:06:45 +0200887#ifdef NC_ENABLED_SSH_TLS
888 /* destroy the certificate expiration notification thread */
889 nc_server_notif_cert_expiration_thread_stop(1);
890 nc_server_config_ln2_netconf_server(NULL, NC_OP_DELETE);
891#endif /* NC_ENABLED_SSH_TLS */
892
romanf578cd52023-10-19 09:47:40 +0200893 nc_server_config_listen(NULL, NC_OP_DELETE);
894 nc_server_config_ch(NULL, NC_OP_DELETE);
895
romana2ff4ef2024-01-19 14:41:46 +0100896 endpt_count = server_opts.endpt_count;
897 for (i = 0; i < endpt_count; i++) {
898 if (server_opts.endpts[i].ti == NC_TI_UNIX) {
899 _nc_server_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]);
900 }
901 }
902
romanf578cd52023-10-19 09:47:40 +0200903 pthread_mutex_destroy(&server_opts.bind_lock);
904
905#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100906 free(server_opts.authkey_path_fmt);
907 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100908 free(server_opts.pam_config_name);
909 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200910 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
911 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
912 }
913 server_opts.interactive_auth_data = NULL;
914 server_opts.interactive_auth_data_free = NULL;
915
romanf578cd52023-10-19 09:47:40 +0200916 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
917 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
918 curl_global_cleanup();
Michal Vasko5788c802024-05-03 16:14:40 +0200919 ssh_finalize();
romanf578cd52023-10-19 09:47:40 +0200920#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100921}
922
Michal Vasko086311b2016-01-08 09:53:11 +0100923API int
924nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
925{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200926 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200927 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200928 return -1;
929 } 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 +0200930 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100931 return -1;
932 }
933
romanf578cd52023-10-19 09:47:40 +0200934 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
935 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100936 return 0;
937}
938
Michal Vasko1a38c862016-01-15 15:50:07 +0100939API void
Michal Vasko55f03972016-04-13 08:56:01 +0200940nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
941{
942 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200943 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200944 return;
945 }
946
947 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200948 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200949 }
950 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200951 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200952 }
953}
954
Michal Vasko55f03972016-04-13 08:56:01 +0200955API int
Radek Krejci658782b2016-12-04 22:04:55 +0100956nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200957{
Michal Vasko93224072021-11-09 12:14:28 +0100958 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100959
960 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200961 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100962 return EXIT_FAILURE;
963 }
964
Michal Vasko93224072021-11-09 12:14:28 +0100965 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200966 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100967 server_opts.capabilities = mem;
968
969 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
970 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100971
972 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200973}
974
Michal Vasko1a38c862016-01-15 15:50:07 +0100975API void
Michal Vasko1440a742021-03-31 11:11:03 +0200976nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
977 void (*free_user_data)(void *user_data))
978{
979 server_opts.content_id_clb = content_id_clb;
980 server_opts.content_id_data = user_data;
981 server_opts.content_id_data_free = free_user_data;
982}
983
Michal Vasko71090fc2016-05-24 16:37:28 +0200984API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100985nc_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 +0100986{
Michal Vasko71090fc2016-05-24 16:37:28 +0200987 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200988 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200989
romand82caf12024-03-05 14:21:39 +0100990 NC_CHECK_ARG_RET(NULL, ctx, username, fdin >= 0, fdout >= 0, session, NC_MSG_ERROR);
romanf578cd52023-10-19 09:47:40 +0200991
romand82caf12024-03-05 14:21:39 +0100992 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
Michal Vasko086311b2016-01-08 09:53:11 +0100993
Michal Vasko93224072021-11-09 12:14:28 +0100994 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200995 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100996
Michal Vasko086311b2016-01-08 09:53:11 +0100997 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200998 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200999 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +01001000 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +01001001
Michal Vasko086311b2016-01-08 09:53:11 +01001002 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +01001003 (*session)->ti_type = NC_TI_FD;
1004 (*session)->ti.fd.in = fdin;
1005 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +01001006
Michal Vasko93224072021-11-09 12:14:28 +01001007 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +01001008 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01001009 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +01001010
Michal Vaskob48aa812016-01-18 14:13:09 +01001011 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02001012 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01001013
Michal Vasko086311b2016-01-08 09:53:11 +01001014 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02001015 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02001016 if (msgtype != NC_MSG_HELLO) {
1017 nc_session_free(*session, NULL);
1018 *session = NULL;
1019 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001020 }
Michal Vasko9fb42272017-10-05 13:50:05 +02001021
Michal Vaskod8a74192023-02-06 15:51:50 +01001022 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02001023 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01001024 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01001025 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +02001026
Michal Vasko1a38c862016-01-15 15:50:07 +01001027 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +01001028
Michal Vasko71090fc2016-05-24 16:37:28 +02001029 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +01001030}
Michal Vasko9e036d52016-01-08 10:49:26 +01001031
Michal Vaskob30b99c2016-07-26 11:35:43 +02001032static void
Michal Vasko74c345f2018-02-07 10:37:11 +01001033nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
1034{
1035 uint8_t q_last;
1036
1037 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
1038 ERRINT;
1039 return;
1040 }
1041
1042 /* get a unique queue value (by adding 1 to the last added value, if any) */
1043 if (ps->queue_len) {
1044 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1045 *id = ps->queue[q_last] + 1;
1046 } else {
1047 *id = 0;
1048 }
1049
1050 /* add the id into the queue */
1051 ++ps->queue_len;
1052 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1053 ps->queue[q_last] = *id;
1054}
1055
1056static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001057nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1058{
Michal Vasko74c345f2018-02-07 10:37:11 +01001059 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001060
1061 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001062 /* get the actual queue idx */
1063 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001064
1065 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001066 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001067 /* another equal value, simply cannot be */
1068 ERRINT;
1069 }
Michal Vaskod8340032018-02-12 14:41:00 +01001070 if (found == 2) {
1071 /* move the following values */
1072 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1073 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001074 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001075 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001076 if (i == 0) {
1077 found = 1;
1078 } else {
1079 /* this is not okay, our id is in the middle of the queue */
1080 found = 2;
1081 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001082 }
1083 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001084 if (!found) {
1085 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001086 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001087 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001088
Michal Vasko103fe632018-02-12 16:37:45 +01001089 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001090 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001091 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001092 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1093 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001094}
1095
Michal Vaskof04a52a2016-04-07 10:52:10 +02001096int
Michal Vasko26043172016-07-26 14:08:59 +02001097nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001098{
1099 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001100 struct timespec ts;
1101
Michal Vaskobe86fe32016-04-07 10:43:03 +02001102 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001103 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001104 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001105 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001106 return -1;
1107 }
1108
Michal Vasko74c345f2018-02-07 10:37:11 +01001109 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001110 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001111 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001112 pthread_mutex_unlock(&ps->lock);
1113 return -1;
1114 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001115
1116 /* add ourselves into the queue */
1117 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001118 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 +02001119 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001120
1121 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001122 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001123 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001124
Michal Vaskod8a74192023-02-06 15:51:50 +01001125 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001126 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301127 /**
1128 * This may happen when another thread releases the lock and broadcasts the condition
1129 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1130 * but when actually this thread was ready for condition.
1131 */
preetbhansali629dfc42018-12-17 16:04:40 +05301132 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301133 break;
1134 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001135
Michal Vasko05532772021-06-03 12:12:38 +02001136 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001137 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001138 nc_ps_queue_remove_id(ps, *id);
1139 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001140 return -1;
1141 }
1142 }
1143
Michal Vaskobe86fe32016-04-07 10:43:03 +02001144 /* UNLOCK */
1145 pthread_mutex_unlock(&ps->lock);
1146
1147 return 0;
1148}
1149
Michal Vaskof04a52a2016-04-07 10:52:10 +02001150int
Michal Vasko26043172016-07-26 14:08:59 +02001151nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001152{
1153 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001154
1155 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001156 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001157 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001158 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001159 ret = -1;
1160 }
1161
Michal Vaskob30b99c2016-07-26 11:35:43 +02001162 /* we must be the first, it was our turn after all, right? */
1163 if (ps->queue[ps->queue_begin] != id) {
1164 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001165 /* UNLOCK */
1166 if (!ret) {
1167 pthread_mutex_unlock(&ps->lock);
1168 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001169 return -1;
1170 }
1171
Michal Vaskobe86fe32016-04-07 10:43:03 +02001172 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001173 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001174 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 +02001175 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001176
1177 /* broadcast to all other threads that the queue moved */
1178 pthread_cond_broadcast(&ps->cond);
1179
Michal Vaskobe86fe32016-04-07 10:43:03 +02001180 /* UNLOCK */
1181 if (!ret) {
1182 pthread_mutex_unlock(&ps->lock);
1183 }
1184
1185 return ret;
1186}
1187
Michal Vasko428087d2016-01-14 16:04:28 +01001188API struct nc_pollsession *
1189nc_ps_new(void)
1190{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001191 struct nc_pollsession *ps;
1192
1193 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001194 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001195 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001196 pthread_mutex_init(&ps->lock, NULL);
1197
1198 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001199}
1200
1201API void
1202nc_ps_free(struct nc_pollsession *ps)
1203{
fanchanghu3d4e7212017-08-09 09:42:30 +08001204 uint16_t i;
1205
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001206 if (!ps) {
1207 return;
1208 }
1209
Michal Vaskobe86fe32016-04-07 10:43:03 +02001210 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001211 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001212 }
1213
fanchanghu3d4e7212017-08-09 09:42:30 +08001214 for (i = 0; i < ps->session_count; i++) {
1215 free(ps->sessions[i]);
1216 }
1217
Michal Vasko428087d2016-01-14 16:04:28 +01001218 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001219 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001220 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001221
Michal Vasko428087d2016-01-14 16:04:28 +01001222 free(ps);
1223}
1224
1225API int
1226nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1227{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001228 uint8_t q_id;
1229
romanf578cd52023-10-19 09:47:40 +02001230 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001231
Michal Vasko48a63ed2016-03-01 09:48:21 +01001232 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001233 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001234 return -1;
1235 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001236
Michal Vasko428087d2016-01-14 16:04:28 +01001237 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001238 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001239 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001240 ERRMEM;
1241 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001242 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001243 return -1;
1244 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001245 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1246 if (!ps->sessions[ps->session_count - 1]) {
1247 ERRMEM;
1248 --ps->session_count;
1249 /* UNLOCK */
1250 nc_ps_unlock(ps, q_id, __func__);
1251 return -1;
1252 }
1253 ps->sessions[ps->session_count - 1]->session = session;
1254 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001255
Michal Vasko48a63ed2016-03-01 09:48:21 +01001256 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001257 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001258}
1259
Michal Vasko48a63ed2016-03-01 09:48:21 +01001260static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001261_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001262{
1263 uint16_t i;
1264
Radek Krejcid5f978f2016-03-03 13:14:45 +01001265 if (index >= 0) {
1266 i = (uint16_t)index;
1267 goto remove;
1268 }
Michal Vasko428087d2016-01-14 16:04:28 +01001269 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001270 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001271remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001272 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001273 if (i <= ps->session_count) {
1274 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001275 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001276 }
1277 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001278 free(ps->sessions);
1279 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001280 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001281 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001282 return 0;
1283 }
1284 }
1285
Michal Vaskof0537d82016-01-29 14:42:38 +01001286 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001287}
1288
Michal Vasko48a63ed2016-03-01 09:48:21 +01001289API int
1290nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1291{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001292 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001293 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001294
romanf578cd52023-10-19 09:47:40 +02001295 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001296
1297 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001298 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001299 return -1;
1300 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001301
Radek Krejcid5f978f2016-03-03 13:14:45 +01001302 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001303
1304 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001305 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001306
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001307 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001308}
1309
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001310API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001311nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001312{
1313 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001314 struct nc_session *ret = NULL;
1315
romanf578cd52023-10-19 09:47:40 +02001316 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001317
1318 /* LOCK */
1319 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1320 return NULL;
1321 }
1322
Michal Vasko4871c9d2017-10-09 14:48:39 +02001323 if (idx < ps->session_count) {
1324 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001325 }
1326
1327 /* UNLOCK */
1328 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1329
1330 return ret;
1331}
1332
Michal Vasko3ec3b112022-07-21 12:32:33 +02001333API struct nc_session *
1334nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1335{
1336 uint8_t q_id;
1337 uint16_t i;
1338 struct nc_session *ret = NULL;
1339
romanf578cd52023-10-19 09:47:40 +02001340 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001341
1342 /* LOCK */
1343 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1344 return NULL;
1345 }
1346
1347 for (i = 0; i < ps->session_count; ++i) {
1348 if (match_cb(ps->sessions[i]->session, cb_data)) {
1349 ret = ps->sessions[i]->session;
1350 break;
1351 }
1352 }
1353
1354 /* UNLOCK */
1355 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1356
1357 return ret;
1358}
1359
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001360API uint16_t
1361nc_ps_session_count(struct nc_pollsession *ps)
1362{
Michal Vasko47003942019-03-14 12:25:23 +01001363 uint8_t q_id;
1364 uint16_t session_count;
1365
romanf578cd52023-10-19 09:47:40 +02001366 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001367
Michal Vasko47003942019-03-14 12:25:23 +01001368 /* LOCK (just for memory barrier so that we read the current value) */
1369 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1370 return 0;
1371 }
1372
1373 session_count = ps->session_count;
1374
1375 /* UNLOCK */
1376 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1377
1378 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001379}
1380
Michal Vasko77e83572022-07-21 15:31:15 +02001381static NC_MSG_TYPE
1382recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1383{
1384 struct lyd_attr *attr;
1385
1386 assert(envp && !envp->schema);
1387
1388 /* find the message-id attribute */
1389 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1390 if (!strcmp(attr->name.name, "message-id")) {
1391 break;
1392 }
1393 }
1394
1395 if (!attr) {
1396 ERR(session, "Received an <rpc> without a message-id.");
1397 return NC_MSG_REPLY_ERR_MSGID;
1398 }
1399
1400 return NC_MSG_RPC;
1401}
1402
aPiecek762e3be2024-06-10 14:44:35 +02001403/**
aPiecekab7f37f2024-07-22 11:24:52 +02001404 * @brief Find lysc node mentioned in schema_path.
1405 *
1406 * @param[in] ctx libyang context.
1407 * @param[in] ly_err last libyang error.
1408 * @return lysc node.
1409 */
1410static const struct lysc_node *
1411nc_rpc_err_find_lysc_node(const struct ly_ctx *ctx, const struct ly_err_item *ly_err)
1412{
1413 char *str, *last;
1414 const struct lysc_node *cn;
1415
1416 if (!ly_err->schema_path) {
1417 return NULL;
1418 }
1419
1420 str = strdup(ly_err->schema_path);
1421 if (!str) {
1422 return NULL;
1423 }
1424 last = strrchr(str, '/');
1425 if (strchr(last, '@')) {
1426 /* ignore attribute part */
1427 *last = '\0';
1428 }
1429 cn = lys_find_path(ctx, NULL, str, 0);
1430 free(str);
1431
1432 return cn;
1433}
1434
1435/**
1436 * @brief Find the nth substring delimited by quotes.
1437 *
1438 * For example: abcd"ef"ghij"kl"mn -> index 0 is "ef", index 1 is "kl".
1439 *
1440 * @param[in] msg Input string with quoted substring.
1441 * @param[in] index Number starting from 0 specifying the nth substring.
1442 * @return Copied nth substring without quotes.
1443 */
1444static char *
1445nc_rpc_err_get_quoted_string(const char *msg, uint32_t index)
1446{
1447 char *ret;
1448 const char *start = NULL, *end = NULL, *iter, *tmp;
1449 uint32_t quote_cnt = 0, last_quote;
1450
1451 assert(msg);
1452
1453 last_quote = (index + 1) * 2;
1454 for (iter = msg; *iter; ++iter) {
1455 if (*iter != '\"') {
1456 continue;
1457 }
1458 /* updating the start and end pointers - swap */
1459 tmp = end;
1460 end = iter;
1461 start = tmp;
1462 if (++quote_cnt == last_quote) {
1463 /* nth substring found */
1464 break;
1465 }
1466 }
1467
1468 if (!start) {
1469 return NULL;
1470 }
1471
1472 /* Skip first quote */
1473 ++start;
1474 /* Copy substring */
1475 ret = strndup(start, end - start);
1476
1477 return ret;
1478}
1479
1480/**
1481 * @brief Check that the @p str starts with the @p prefix.
1482 *
1483 * @param[in] prefix Required prefix.
1484 * @param[in] str Input string to check.
1485 * @return True if @p str start with @p prefix otherwise False.
1486 */
1487static ly_bool
1488nc_strstarts(const char *prefix, const char *str)
1489{
1490 return strncmp(str, prefix, strlen(prefix)) == 0;
1491}
1492
1493/**
aPiecek762e3be2024-06-10 14:44:35 +02001494 * @brief Prepare reply for rpc error.
1495 *
1496 * @param[in] session NETCONF session.
1497 * @param[in] envp NETCONF-specific RPC envelope. Can be NULL.
1498 * @return rpc-reply object or NULL.
1499 */
1500static struct nc_server_reply *
1501nc_server_prepare_rpc_err(struct nc_session *session, struct lyd_node *envp)
1502{
aPiecekab7f37f2024-07-22 11:24:52 +02001503 struct lyd_node *reply = NULL;
1504 const struct lysc_node *cn;
aPiecek762e3be2024-06-10 14:44:35 +02001505 const struct ly_err_item *ly_err;
aPiecekab7f37f2024-07-22 11:24:52 +02001506 NC_ERR_TYPE errtype;
1507 const char *attr;
1508 char *str = NULL, *errmsg = NULL, *schema_path = NULL;
1509 LY_ERR errcode;
aPiecek762e3be2024-06-10 14:44:35 +02001510
aPiecekab7f37f2024-07-22 11:24:52 +02001511 /* envelope was not parsed */
aPiecek762e3be2024-06-10 14:44:35 +02001512 if (!envp && (session->version != NC_VERSION_11)) {
1513 return NULL;
1514 }
aPiecek762e3be2024-06-10 14:44:35 +02001515 ly_err = ly_err_last(session->ctx);
aPiecekab7f37f2024-07-22 11:24:52 +02001516 if (!envp && !strcmp("Missing XML namespace.", ly_err->msg)) {
1517 reply = nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "xmlns", "rpc");
1518 goto cleanup;
1519 } else if (!envp) {
aPiecek762e3be2024-06-10 14:44:35 +02001520 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1521 * the server (RFC 6241 sec. 3) */
aPiecekab7f37f2024-07-22 11:24:52 +02001522 reply = nc_err(session->ctx, NC_ERR_MALFORMED_MSG);
1523 return nc_server_reply_err(reply);
1524 }
1525 /* at least the envelopes were parsed */
1526 assert(envp);
1527
1528 /* store strings, to avoid overwriting ly_err */
1529 errmsg = strdup(ly_err->msg);
1530 if (!errmsg) {
1531 reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1532 goto cleanup;
1533 }
1534 if (ly_err->schema_path) {
1535 schema_path = strdup(ly_err->schema_path);
1536 if (!schema_path) {
1537 reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1538 goto cleanup;
1539 }
1540 }
1541 errcode = ly_err->err;
1542
1543 /* find out in which layer the error occurred */
1544 cn = nc_rpc_err_find_lysc_node(session->ctx, ly_err);
1545 if (cn && ((cn->nodetype & LYS_RPC) || (cn->nodetype & LYS_INPUT))) {
1546 errtype = NC_ERR_TYPE_PROT;
1547 } else {
1548 errtype = NC_ERR_TYPE_APP;
aPiecek762e3be2024-06-10 14:44:35 +02001549 }
1550
aPiecekab7f37f2024-07-22 11:24:52 +02001551 /* deciding which error to prepare */
1552 if (cn && (nc_strstarts("Missing mandatory prefix", errmsg) ||
1553 nc_strstarts("Unknown XML prefix", errmsg))) {
1554 str = nc_rpc_err_get_quoted_string(errmsg, 1);
1555 reply = str ? nc_err(session->ctx, NC_ERR_UNKNOWN_ATTR, errtype, str, cn->name) :
1556 nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1557 } else if (cn && nc_strstarts("Annotation definition for attribute", errmsg)) {
1558 attr = strrchr(schema_path, ':') + 1;
1559 reply = nc_err(session->ctx, NC_ERR_UNKNOWN_ATTR, errtype, attr, cn->name);
1560 } else if (nc_strstarts("Invalid character sequence", errmsg)) {
1561 reply = nc_err(session->ctx, NC_ERR_MALFORMED_MSG);
1562 } else if (errcode == LY_EMEM) {
1563 /* <error-tag>resource-denied</error-tag> */
1564 reply = nc_err(session->ctx, NC_ERR_RES_DENIED, errtype);
1565 } else {
1566 /* prepare some generic error */
1567 reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1568 }
1569
1570cleanup:
1571 nc_err_set_msg(reply, errmsg, "en");
1572
1573 /* clear for other errors */
1574 ly_err_clean(session->ctx, NULL);
1575
1576 free(errmsg);
1577 free(schema_path);
1578 free(str);
1579
1580 return nc_server_reply_err(reply);
aPiecek762e3be2024-06-10 14:44:35 +02001581}
1582
Michal Vasko131120a2018-05-29 15:44:02 +02001583/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001584 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001585 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001586 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001587 * NC_PSPOLL_RPC
1588 */
1589static int
Michal Vasko131120a2018-05-29 15:44:02 +02001590nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001591{
Michal Vasko77367452021-02-16 16:32:18 +01001592 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001593 struct nc_server_reply *reply = NULL;
Michal Vaskof8fba542023-10-23 12:03:50 +02001594 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001595
romanf578cd52023-10-19 09:47:40 +02001596 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1597
1598 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001599 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001600 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001601 }
1602
Michal Vasko93224072021-11-09 12:14:28 +01001603 *rpc = NULL;
1604
Michal Vasko77367452021-02-16 16:32:18 +01001605 /* get a message */
1606 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1607 if (r == -2) {
1608 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001609 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001610 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001611 }
1612 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001613 return NC_PSPOLL_ERROR;
1614 } else if (!r) {
1615 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001616 }
1617
Michal Vasko77367452021-02-16 16:32:18 +01001618 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001619 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001620
1621 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001622 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1623 /* check message-id */
1624 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1625 /* valid RPC */
1626 ret = NC_PSPOLL_RPC;
1627 } else {
1628 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001629 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
aPiecek762e3be2024-06-10 14:44:35 +02001630 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77e83572022-07-21 15:31:15 +02001631 }
1632 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001633 /* bad RPC received */
aPiecek762e3be2024-06-10 14:44:35 +02001634 reply = nc_server_prepare_rpc_err(session, (*rpc)->envp);
1635 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001636 }
1637
1638cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001639 if (reply) {
1640 /* send error reply */
1641 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1642 nc_server_reply_free(reply);
1643 if (r != NC_MSG_REPLY) {
1644 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1645 if (session->status != NC_STATUS_INVALID) {
1646 session->status = NC_STATUS_INVALID;
1647 session->term_reason = NC_SESSION_TERM_OTHER;
1648 }
1649 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001650
1651 /* bad RPC and an error reply sent */
aPiecek762e3be2024-06-10 14:44:35 +02001652 ret |= NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001653 }
1654
Michal Vasko77367452021-02-16 16:32:18 +01001655 ly_in_free(msg, 1);
1656 if (ret != NC_PSPOLL_RPC) {
1657 nc_server_rpc_free(*rpc);
1658 *rpc = NULL;
1659 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001660 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001661}
1662
fanchanghu966f2de2016-07-21 02:28:57 -04001663API void
1664nc_set_global_rpc_clb(nc_rpc_clb clb)
1665{
1666 global_rpc_clb = clb;
1667}
1668
Radek Krejci93e80222016-10-03 13:34:25 +02001669API NC_MSG_TYPE
1670nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1671{
Michal Vasko131120a2018-05-29 15:44:02 +02001672 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001673
1674 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001675 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001676 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001677 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001678 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001679 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001680 return NC_MSG_ERROR;
1681 }
1682
Michal Vasko131120a2018-05-29 15:44:02 +02001683 /* we do not need RPC lock for this, IO lock will be acquired properly */
1684 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001685 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001686 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001687 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001688
Michal Vasko131120a2018-05-29 15:44:02 +02001689 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001690}
1691
Michal Vaskof9467762023-03-28 09:02:08 +02001692/**
1693 * @brief Send a reply acquiring IO lock as needed.
1694 * Session RPC lock must be held!
1695 *
1696 * @param[in] session Session to use.
1697 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1698 * @param[in] rpc RPC to sent.
1699 * @return 0 on success.
1700 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1701 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001702 */
1703static int
Michal Vasko93224072021-11-09 12:14:28 +01001704nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001705{
1706 nc_rpc_clb clb;
1707 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001708 const struct lysc_node *rpc_act = NULL;
1709 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001710 int ret = 0;
1711 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001712
Michal Vasko4a827e52016-03-03 10:59:00 +01001713 if (!rpc) {
1714 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001715 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001716 }
1717
Michal Vasko77367452021-02-16 16:32:18 +01001718 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001719 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001720 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001721 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001722 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001723 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001724 if (elem->schema->nodetype == LYS_ACTION) {
1725 rpc_act = elem->schema;
1726 break;
1727 }
Michal Vasko77367452021-02-16 16:32:18 +01001728 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001729 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001730 if (!rpc_act) {
1731 ERRINT;
1732 return NC_PSPOLL_ERROR;
1733 }
1734 }
1735
1736 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001737 if (!global_rpc_clb) {
1738 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001739 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 +03001740 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001741 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001742 }
Michal Vasko428087d2016-01-14 16:04:28 +01001743 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001744 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001745 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001746 }
1747
1748 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001749 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001750 }
Michal Vasko77367452021-02-16 16:32:18 +01001751 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001752 if (reply->type == NC_RPL_ERROR) {
1753 ret |= NC_PSPOLL_REPLY_ERROR;
1754 }
1755 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001756
Michal Vasko131120a2018-05-29 15:44:02 +02001757 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001758 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001759 ret |= NC_PSPOLL_ERROR;
1760 }
Michal Vasko428087d2016-01-14 16:04:28 +01001761
1762 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1763 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1764 session->status = NC_STATUS_INVALID;
1765 }
1766
Michal Vasko71090fc2016-05-24 16:37:28 +02001767 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001768}
1769
Michal Vaskof9467762023-03-28 09:02:08 +02001770/**
1771 * @brief Poll a session from pspoll acquiring IO lock as needed.
1772 * Session must be running and session RPC lock held!
1773 *
1774 * @param[in] session Session to use.
1775 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1776 * @param[in] now_mono Current monotonic timestamp.
1777 * @param[in,out] msg Message to fill in case of an error.
1778 * @return NC_PSPOLL_RPC if some application data are available.
1779 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1780 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1781 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1782 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1783 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001784 */
1785static int
Michal Vasko131120a2018-05-29 15:44:02 +02001786nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001787{
Michal Vasko9a327362017-01-11 11:31:46 +01001788 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001789 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001790
romanf578cd52023-10-19 09:47:40 +02001791#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001792 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001793 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001794#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001795
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001796 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001797 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001798 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001799 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001800 session->status = NC_STATUS_INVALID;
1801 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1802 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1803 }
1804
Michal Vasko131120a2018-05-29 15:44:02 +02001805 r = nc_session_io_lock(session, io_timeout, __func__);
1806 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001807 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001808 return NC_PSPOLL_ERROR;
1809 } else if (!r) {
1810 return NC_PSPOLL_TIMEOUT;
1811 }
1812
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001813 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001814#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02001815 case NC_TI_SSH:
romanf578cd52023-10-19 09:47:40 +02001816 ssh_msg = ssh_message_get(session->ti.libssh.session);
1817 if (ssh_msg) {
1818 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1819 if (session->ti.libssh.next) {
1820 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1821 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1822 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1823 /* new NETCONF SSH channel */
1824 ret = NC_PSPOLL_SSH_CHANNEL;
1825 break;
1826 }
1827 }
1828 if (new != session) {
1829 ssh_message_free(ssh_msg);
1830 break;
1831 }
1832 }
1833 if (!ret) {
1834 /* just some SSH message */
1835 ret = NC_PSPOLL_SSH_MSG;
1836 }
1837 ssh_message_free(ssh_msg);
1838
1839 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1840 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1841 */
1842 break;
1843 }
1844
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001845 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001846 if (r == SSH_EOF) {
1847 sprintf(msg, "SSH channel unexpected EOF");
1848 session->status = NC_STATUS_INVALID;
1849 session->term_reason = NC_SESSION_TERM_DROPPED;
1850 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1851 } else if (r == SSH_ERROR) {
1852 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001853 session->status = NC_STATUS_INVALID;
1854 session->term_reason = NC_SESSION_TERM_OTHER;
1855 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001856 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001857 /* no application data received */
1858 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001859 } else {
1860 /* we have some application data */
1861 ret = NC_PSPOLL_RPC;
1862 }
1863 break;
roman506354a2024-04-11 09:37:22 +02001864 case NC_TI_TLS:
romance435112024-04-23 15:12:09 +02001865 r = nc_tls_get_num_pending_bytes_wrap(session->ti.tls.session);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001866 if (!r) {
1867 /* no data pending in the SSL buffer, poll fd */
roman9225e912024-04-05 12:33:09 +02001868 pfd.fd = nc_tls_get_fd_wrap(session);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001869 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001870 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001871 ret = NC_PSPOLL_ERROR;
1872 break;
1873 }
1874 pfd.events = POLLIN;
1875 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001876 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001877
Michal Vasko63b92d62024-04-29 10:04:56 +02001878 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001879 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001880 session->status = NC_STATUS_INVALID;
1881 ret = NC_PSPOLL_ERROR;
1882 } else if (r > 0) {
1883 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001884 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001885 session->status = NC_STATUS_INVALID;
1886 session->term_reason = NC_SESSION_TERM_DROPPED;
1887 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1888 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001889 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001890 session->status = NC_STATUS_INVALID;
1891 session->term_reason = NC_SESSION_TERM_OTHER;
1892 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1893 } else {
1894 ret = NC_PSPOLL_RPC;
1895 }
1896 } else {
1897 ret = NC_PSPOLL_TIMEOUT;
1898 }
1899 } else {
1900 ret = NC_PSPOLL_RPC;
1901 }
1902 break;
romanf578cd52023-10-19 09:47:40 +02001903#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001904 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001905 case NC_TI_UNIX:
1906 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001907 pfd.events = POLLIN;
1908 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001909 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001910
Michal Vasko63b92d62024-04-29 10:04:56 +02001911 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001912 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001913 session->status = NC_STATUS_INVALID;
1914 ret = NC_PSPOLL_ERROR;
1915 } else if (r > 0) {
1916 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001917 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001918 session->status = NC_STATUS_INVALID;
1919 session->term_reason = NC_SESSION_TERM_DROPPED;
1920 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1921 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001922 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001923 session->status = NC_STATUS_INVALID;
1924 session->term_reason = NC_SESSION_TERM_OTHER;
1925 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1926 } else {
1927 ret = NC_PSPOLL_RPC;
1928 }
1929 } else {
1930 ret = NC_PSPOLL_TIMEOUT;
1931 }
1932 break;
1933 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001934 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001935 ret = NC_PSPOLL_ERROR;
1936 break;
1937 }
1938
Michal Vasko131120a2018-05-29 15:44:02 +02001939 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001940 return ret;
1941}
1942
Michal Vasko6a445db2024-06-06 15:58:21 +02001943/**
1944 * @brief Poll a single pspoll session.
1945 *
1946 * @param[in] ps_session pspoll session to poll.
1947 * @param[in] now_mono Current monotonic timestamp.
1948 * @return NC_PSPOLL_RPC if some application data are available.
1949 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1950 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1951 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1952 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated.
1953 * @return NC_PSPOLL_ERROR on other fatal errors.
1954 */
1955static int
1956nc_ps_poll_sess(struct nc_ps_session *ps_session, time_t now_mono)
1957{
1958 int ret = NC_PSPOLL_ERROR;
1959 char msg[256];
1960
1961 switch (ps_session->state) {
1962 case NC_PS_STATE_NONE:
1963 if (ps_session->session->status == NC_STATUS_RUNNING) {
1964 /* session is fine, work with it */
1965 ps_session->state = NC_PS_STATE_BUSY;
1966
1967 ret = nc_ps_poll_session_io(ps_session->session, NC_SESSION_LOCK_TIMEOUT, now_mono, msg);
1968 switch (ret) {
1969 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
1970 ERR(ps_session->session, "%s.", msg);
1971 ps_session->state = NC_PS_STATE_INVALID;
1972 break;
1973 case NC_PSPOLL_ERROR:
1974 ERR(ps_session->session, "%s.", msg);
1975 ps_session->state = NC_PS_STATE_NONE;
1976 break;
1977 case NC_PSPOLL_TIMEOUT:
1978#ifdef NC_ENABLED_SSH_TLS
1979 case NC_PSPOLL_SSH_CHANNEL:
1980 case NC_PSPOLL_SSH_MSG:
1981#endif /* NC_ENABLED_SSH_TLS */
1982 ps_session->state = NC_PS_STATE_NONE;
1983 break;
1984 case NC_PSPOLL_RPC:
1985 /* let's keep the state busy, we are not done with this session */
1986 break;
1987 }
1988 } else {
1989 /* session is not fine, let the caller know */
1990 ret = NC_PSPOLL_SESSION_TERM;
1991 if (ps_session->session->term_reason != NC_SESSION_TERM_CLOSED) {
1992 ret |= NC_PSPOLL_SESSION_ERROR;
1993 }
1994 ps_session->state = NC_PS_STATE_INVALID;
1995 }
1996 break;
1997 case NC_PS_STATE_BUSY:
1998 /* it definitely should not be busy because we have the lock */
1999 ERRINT;
2000 ret = NC_PSPOLL_ERROR;
2001 break;
2002 case NC_PS_STATE_INVALID:
2003 /* we got it locked, but it will be freed, let it be */
2004 ret = NC_PSPOLL_TIMEOUT;
2005 break;
2006 }
2007
2008 return ret;
2009}
2010
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002011API int
2012nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
2013{
Michal Vasko443faa02022-10-20 09:09:03 +02002014 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002015 uint8_t q_id;
2016 uint16_t i, j;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002017 struct timespec ts_timeout, ts_cur;
2018 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08002019 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002020 struct nc_server_rpc *rpc = NULL;
2021
romanf578cd52023-10-19 09:47:40 +02002022 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01002023
Michal Vaskodad35372024-06-06 16:01:30 +02002024 if (session) {
2025 *session = NULL;
2026 }
2027
Michal Vaskoade892d2017-02-22 13:40:35 +01002028 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02002029 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002030 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02002031 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01002032
Michal Vaskoade892d2017-02-22 13:40:35 +01002033 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002034 nc_ps_unlock(ps, q_id, __func__);
2035 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01002036 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01002037
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002038 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01002039 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01002040 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01002041 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01002042 }
2043
2044 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01002045 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002046 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01002047 if (ps->last_event_session == ps->session_count - 1) {
2048 i = j = 0;
2049 } else {
2050 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01002051 }
Michal Vasko9a327362017-01-11 11:31:46 +01002052 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08002053 cur_ps_session = ps->sessions[i];
2054 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01002055
Michal Vasko131120a2018-05-29 15:44:02 +02002056 /* SESSION RPC LOCK */
2057 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002058 if (r == -1) {
2059 ret = NC_PSPOLL_ERROR;
2060 } else if (r == 1) {
2061 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vasko6a445db2024-06-06 15:58:21 +02002062 ret = nc_ps_poll_sess(cur_ps_session, ts_timeout.tv_sec);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002063
Michal Vasko131120a2018-05-29 15:44:02 +02002064 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002065 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02002066 /* SESSION RPC UNLOCK */
2067 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01002068 }
Michal Vaskoade892d2017-02-22 13:40:35 +01002069 } else {
2070 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002071 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01002072 }
Michal Vasko428087d2016-01-14 16:04:28 +01002073
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002074 /* something happened */
2075 if (ret != NC_PSPOLL_TIMEOUT) {
2076 break;
2077 }
2078
Michal Vasko9a327362017-01-11 11:31:46 +01002079 if (i == ps->session_count - 1) {
2080 i = 0;
2081 } else {
2082 ++i;
2083 }
2084 } while (i != j);
2085
Michal Vaskoade892d2017-02-22 13:40:35 +01002086 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002087 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01002088 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002089
Michal Vaskod8a74192023-02-06 15:51:50 +01002090 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002091 /* final timeout */
2092 break;
Michal Vasko9a327362017-01-11 11:31:46 +01002093 }
Michal Vasko428087d2016-01-14 16:04:28 +01002094 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002095 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01002096
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002097 /* do we want to return the session? */
2098 switch (ret) {
2099 case NC_PSPOLL_RPC:
2100 case NC_PSPOLL_SESSION_TERM:
2101 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02002102#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002103 case NC_PSPOLL_SSH_CHANNEL:
2104 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02002105#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002106 if (session) {
2107 *session = cur_session;
2108 }
2109 ps->last_event_session = i;
2110 break;
2111 default:
2112 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02002113 }
Michal Vasko428087d2016-01-14 16:04:28 +01002114
Michal Vaskoade892d2017-02-22 13:40:35 +01002115 /* PS UNLOCK */
2116 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01002117
Michal Vasko131120a2018-05-29 15:44:02 +02002118 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002119 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02002120 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002121 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
2122 if (cur_session->status != NC_STATUS_RUNNING) {
2123 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08002124 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002125 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08002126 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002127 }
2128 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02002129 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002130
Michal Vasko7f1ee932018-10-11 09:41:42 +02002131 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02002132 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002133 if (cur_session->status != NC_STATUS_RUNNING) {
2134 ret |= NC_PSPOLL_SESSION_TERM;
2135 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
2136 ret |= NC_PSPOLL_SESSION_ERROR;
2137 }
fanchanghu3d4e7212017-08-09 09:42:30 +08002138 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002139 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08002140 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002141 }
Michal Vasko428087d2016-01-14 16:04:28 +01002142 }
Michal Vasko77367452021-02-16 16:32:18 +01002143 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02002144
Michal Vasko131120a2018-05-29 15:44:02 +02002145 /* SESSION RPC UNLOCK */
2146 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01002147 }
2148
Michal Vasko48a63ed2016-03-01 09:48:21 +01002149 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01002150}
2151
Michal Vaskod09eae62016-02-01 10:32:52 +01002152API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002153nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01002154{
Michal Vaskob30b99c2016-07-26 11:35:43 +02002155 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01002156 uint16_t i;
2157 struct nc_session *session;
2158
Michal Vasko9a25e932016-02-01 10:36:42 +01002159 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02002160 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01002161 return;
2162 }
2163
Michal Vasko48a63ed2016-03-01 09:48:21 +01002164 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02002165 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02002166 return;
2167 }
Michal Vaskod09eae62016-02-01 10:32:52 +01002168
Michal Vasko48a63ed2016-03-01 09:48:21 +01002169 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01002170 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08002171 nc_session_free(ps->sessions[i]->session, data_free);
2172 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01002173 }
2174 free(ps->sessions);
2175 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01002176 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01002177 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01002178 } else {
2179 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08002180 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
2181 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01002182 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002183 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01002184 continue;
2185 }
2186
2187 ++i;
2188 }
Michal Vaskod09eae62016-02-01 10:32:52 +01002189 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01002190
2191 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02002192 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01002193}
2194
romanfb3f7cf2023-11-30 16:10:09 +01002195int
2196nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
2197{
2198 int sock = -1, set_addr, ret = 0;
2199
2200 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
2201
2202 if (address) {
2203 set_addr = 1;
2204 } else {
2205 set_addr = 0;
2206 }
2207
2208 if (set_addr) {
2209 port = bind->port;
2210 } else {
2211 address = bind->address;
2212 }
2213
2214 /* we have all the information we need to create a listening socket */
2215 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
2216 /* create new socket, close the old one */
2217 if (endpt->ti == NC_TI_UNIX) {
2218 sock = nc_sock_listen_unix(endpt->opts.unixsock);
2219 } else {
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02002220 sock = nc_sock_listen_inet(address, port);
romanfb3f7cf2023-11-30 16:10:09 +01002221 }
2222
2223 if (sock == -1) {
2224 ret = 1;
2225 goto cleanup;
2226 }
2227
2228 if (bind->sock > -1) {
2229 close(bind->sock);
2230 }
2231 bind->sock = sock;
2232 }
2233
2234 if (sock > -1) {
2235 switch (endpt->ti) {
2236 case NC_TI_UNIX:
2237 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2238 break;
2239#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02002240 case NC_TI_SSH:
romanfb3f7cf2023-11-30 16:10:09 +01002241 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2242 break;
roman506354a2024-04-11 09:37:22 +02002243 case NC_TI_TLS:
romanfb3f7cf2023-11-30 16:10:09 +01002244 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2245 break;
2246#endif /* NC_ENABLED_SSH_TLS */
2247 default:
2248 ERRINT;
2249 ret = 1;
2250 break;
2251 }
2252 }
2253
2254cleanup:
2255 return ret;
2256}
2257
Michal Vasko29f2f022024-03-13 09:06:48 +01002258#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
2259
Michal Vasko6f865982023-11-21 12:10:42 +01002260/**
2261 * @brief Get UID of the owner of a socket.
2262 *
2263 * @param[in] sock Socket to analyze.
2264 * @param[out] uid Socket owner UID.
2265 * @return 0 on success,
2266 * @return -1 on error.
2267 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002268static int
apropp-molex4e903c32020-04-20 03:06:58 -04002269nc_get_uid(int sock, uid_t *uid)
2270{
Michal Vasko6f865982023-11-21 12:10:42 +01002271 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002272
Michal Vaskod3910912020-04-20 09:12:49 +02002273#ifdef SO_PEERCRED
2274 struct ucred ucred;
2275 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002276
Michal Vaskod3910912020-04-20 09:12:49 +02002277 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002278 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2279 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002280 *uid = ucred.uid;
2281 }
2282#else
Michal Vasko6f865982023-11-21 12:10:42 +01002283 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002284#endif
2285
Michal Vasko6f865982023-11-21 12:10:42 +01002286 if (r < 0) {
2287 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002288 return -1;
2289 }
apropp-molex4e903c32020-04-20 03:06:58 -04002290 return 0;
2291}
2292
Michal Vasko29f2f022024-03-13 09:06:48 +01002293#endif
2294
Michal Vasko73556782024-05-27 13:25:21 +02002295/**
2296 * @brief Fully accept a session on a connected UNIX socket.
2297 *
2298 * @param[in] session Session to use.
2299 * @param[in] sock Connected socket.
2300 * @return 1 on success.
2301 * @return -1 on error.
2302 */
apropp-molex4e903c32020-04-20 03:06:58 -04002303static int
Michal Vasko73556782024-05-27 13:25:21 +02002304nc_accept_unix_session(struct nc_session *session, int sock)
Michal Vasko5f352c52019-07-10 16:12:06 +02002305{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002306#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002307 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002308 char *username;
Michal Vasko143aa142021-10-01 15:31:48 +02002309 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002310 char *buf = NULL;
2311 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002312
Michal Vaskod3910912020-04-20 09:12:49 +02002313 if (nc_get_uid(sock, &uid)) {
2314 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002315 return -1;
2316 }
2317
romanf6e32012023-04-24 15:51:26 +02002318 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002319 if (pw == NULL) {
Michal Vasko73556782024-05-27 13:25:21 +02002320 ERR(session, "Failed to find username for uid=%u (%s).", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002321 close(sock);
2322 return -1;
2323 }
2324
2325 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002326 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002327 if (username == NULL) {
2328 ERRMEM;
2329 close(sock);
2330 return -1;
2331 }
Michal Vasko5f352c52019-07-10 16:12:06 +02002332
Michal Vasko73556782024-05-27 13:25:21 +02002333 session->username = username;
2334 session->ti_type = NC_TI_UNIX;
Michal Vasko5f352c52019-07-10 16:12:06 +02002335 session->ti.unixsock.sock = sock;
2336
2337 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002338#else
Michal Vasko29f2f022024-03-13 09:06:48 +01002339 (void)session;
2340 (void)sock;
2341
Michal Vasko73556782024-05-27 13:25:21 +02002342 ERR(session, "Unable to learn the identity of the client connected to the UNIX socket, terminating.");
Claus Klein22091912020-01-20 13:45:47 +01002343 return -1;
2344#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002345}
2346
Michal Vaskoe2713da2016-08-22 16:06:40 +02002347API int
romanfb3f7cf2023-11-30 16:10:09 +01002348nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2349{
2350 int ret = 0;
2351 void *tmp;
2352 uint16_t i;
2353
2354 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2355
2356 /* CONFIG LOCK */
2357 pthread_rwlock_wrlock(&server_opts.config_lock);
2358
2359 /* check name uniqueness */
2360 for (i = 0; i < server_opts.endpt_count; i++) {
2361 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2362 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2363 ret = 1;
2364 goto cleanup;
2365 }
2366 }
2367
2368 /* alloc a new endpoint */
2369 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2370 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2371 server_opts.endpts = tmp;
2372 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2373
2374 /* alloc a new bind */
2375 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2376 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2377 server_opts.binds = tmp;
2378 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2379 server_opts.binds[server_opts.endpt_count].sock = -1;
2380 server_opts.endpt_count++;
2381
2382 /* set name and ti */
2383 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2384 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2385 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2386
2387 /* set the bind data */
2388 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2389 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2390
2391 /* alloc unix opts */
2392 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2393 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2394
2395 /* set the opts data */
2396 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2397 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2398 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2399 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2400 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2401
2402 /* start listening */
2403 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2404 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2405 if (ret) {
2406 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2407 goto cleanup;
2408 }
2409
2410cleanup:
2411 /* CONFIG UNLOCK */
2412 pthread_rwlock_unlock(&server_opts.config_lock);
2413 return ret;
2414}
2415
2416static void
2417nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2418{
2419 if (bind->sock > -1) {
2420 close(bind->sock);
2421 }
2422
2423 unlink(bind->address);
2424 free(bind->address);
2425 free(opts->address);
2426
2427 free(opts);
2428}
2429
2430void
2431_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2432{
2433 free(endpt->name);
2434 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2435
2436 server_opts.endpt_count--;
2437 if (!server_opts.endpt_count) {
2438 free(server_opts.endpts);
2439 free(server_opts.binds);
2440 server_opts.endpts = NULL;
2441 server_opts.binds = NULL;
2442 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2443 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2444 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2445 }
2446}
2447
2448API void
2449nc_server_del_endpt_unix_socket(const char *endpt_name)
2450{
2451 uint16_t i;
2452 struct nc_endpt *endpt = NULL;
2453 struct nc_bind *bind;
2454
romanb6ad37a2023-12-07 13:08:46 +01002455 NC_CHECK_ARG_RET(NULL, endpt_name, );
2456
romanfb3f7cf2023-11-30 16:10:09 +01002457 /* CONFIG LOCK */
2458 pthread_rwlock_wrlock(&server_opts.config_lock);
2459
romanfb3f7cf2023-11-30 16:10:09 +01002460 for (i = 0; i < server_opts.endpt_count; i++) {
2461 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2462 endpt = &server_opts.endpts[i];
2463 bind = &server_opts.binds[i];
2464 break;
2465 }
2466 }
2467 if (!endpt) {
2468 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2469 goto end;
2470 }
2471 if (endpt->ti != NC_TI_UNIX) {
2472 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2473 goto end;
2474 }
2475
2476 _nc_server_del_endpt_unix_socket(endpt, bind);
2477
2478end:
2479 /* CONFIG UNLOCK */
2480 pthread_rwlock_unlock(&server_opts.config_lock);
2481}
2482
2483API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002484nc_server_endpt_count(void)
2485{
2486 return server_opts.endpt_count;
2487}
2488
Michal Vasko71090fc2016-05-24 16:37:28 +02002489API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002490nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002491{
Michal Vasko71090fc2016-05-24 16:37:28 +02002492 NC_MSG_TYPE msgtype;
Michal Vasko87b42562024-05-03 12:02:01 +02002493 int sock = -1, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002494 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002495 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002496 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002497
romanf578cd52023-10-19 09:47:40 +02002498 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002499
romand82caf12024-03-05 14:21:39 +01002500 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
2501
2502 *session = NULL;
2503
Michal Vasko93224072021-11-09 12:14:28 +01002504 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002505 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002506
romanf578cd52023-10-19 09:47:40 +02002507 /* CONFIG LOCK */
2508 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002509
2510 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002511 ERR(NULL, "No endpoints to accept sessions on.");
Michal Vasko87b42562024-05-03 12:02:01 +02002512 msgtype = NC_MSG_ERROR;
2513 goto cleanup;
Michal Vasko51e514d2016-02-02 15:51:52 +01002514 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002515
Michal Vasko8b1740f2024-06-28 13:47:19 +02002516 ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, &server_opts.bind_lock, timeout, &host,
2517 &port, &bind_idx, &sock);
2518 if (ret < 1) {
Michal Vasko87b42562024-05-03 12:02:01 +02002519 msgtype = (!ret ? NC_MSG_WOULDBLOCK : NC_MSG_ERROR);
2520 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002521 }
2522
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02002523 /* configure keepalives */
2524 if (nc_sock_configure_ka(sock, &server_opts.endpts[bind_idx].ka)) {
2525 msgtype = NC_MSG_ERROR;
2526 goto cleanup;
2527 }
2528
Michal Vasko131120a2018-05-29 15:44:02 +02002529 *session = nc_new_session(NC_SERVER, 0);
Michal Vasko87b42562024-05-03 12:02:01 +02002530 NC_CHECK_ERRMEM_GOTO(!(*session), msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002531 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002532 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002533 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002534 (*session)->host = host;
Michal Vasko87b42562024-05-03 12:02:01 +02002535 host = NULL;
Michal Vasko1a38c862016-01-15 15:50:07 +01002536 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002537
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002538 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002539#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +02002540 if (server_opts.endpts[bind_idx].ti == NC_TI_SSH) {
romanf578cd52023-10-19 09:47:40 +02002541 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002542 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002543 if (ret < 0) {
2544 msgtype = NC_MSG_ERROR;
2545 goto cleanup;
2546 } else if (!ret) {
2547 msgtype = NC_MSG_WOULDBLOCK;
2548 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002549 }
roman506354a2024-04-11 09:37:22 +02002550 } else if (server_opts.endpts[bind_idx].ti == NC_TI_TLS) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002551 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002552 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002553 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002554 if (ret < 0) {
2555 msgtype = NC_MSG_ERROR;
2556 goto cleanup;
2557 } else if (!ret) {
2558 msgtype = NC_MSG_WOULDBLOCK;
2559 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002560 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002561 } else
romanf578cd52023-10-19 09:47:40 +02002562#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002563 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2564 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
Michal Vasko73556782024-05-27 13:25:21 +02002565 ret = nc_accept_unix_session(*session, sock);
Michal Vasko87b42562024-05-03 12:02:01 +02002566 sock = -1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002567 if (ret < 0) {
2568 msgtype = NC_MSG_ERROR;
2569 goto cleanup;
2570 }
2571 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002572 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02002573 msgtype = NC_MSG_ERROR;
2574 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002575 }
2576
Michal Vasko2cc4c682016-03-01 09:16:48 +01002577 (*session)->data = NULL;
2578
romanf578cd52023-10-19 09:47:40 +02002579 /* CONFIG UNLOCK */
2580 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002581
Michal Vaskob48aa812016-01-18 14:13:09 +01002582 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002583 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002584
Michal Vasko9e036d52016-01-08 10:49:26 +01002585 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002586 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002587 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002588 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002589 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002590 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002591 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002592
Michal Vaskod8a74192023-02-06 15:51:50 +01002593 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002594 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002595 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002596 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002597 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002598
Michal Vasko71090fc2016-05-24 16:37:28 +02002599 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002600
Michal Vasko71090fc2016-05-24 16:37:28 +02002601cleanup:
romanf578cd52023-10-19 09:47:40 +02002602 /* CONFIG UNLOCK */
2603 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002604
Michal Vasko87b42562024-05-03 12:02:01 +02002605 free(host);
2606 if (sock > -1) {
2607 close(sock);
2608 }
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002609 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002610 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002611 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002612}
2613
romanf578cd52023-10-19 09:47:40 +02002614#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002615
2616API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002617nc_server_ch_is_client(const char *name)
2618{
2619 uint16_t i;
2620 int found = 0;
2621
2622 if (!name) {
2623 return found;
2624 }
2625
2626 /* READ LOCK */
2627 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2628
2629 /* check name uniqueness */
2630 for (i = 0; i < server_opts.ch_client_count; ++i) {
2631 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2632 found = 1;
2633 break;
2634 }
2635 }
2636
2637 /* UNLOCK */
2638 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2639
2640 return found;
2641}
2642
2643API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002644nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2645{
2646 uint16_t i;
2647 struct nc_ch_client *client = NULL;
2648 int found = 0;
2649
2650 if (!client_name || !endpt_name) {
2651 return found;
2652 }
2653
2654 /* READ LOCK */
2655 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2656
2657 for (i = 0; i < server_opts.ch_client_count; ++i) {
2658 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2659 client = &server_opts.ch_clients[i];
2660 break;
2661 }
2662 }
2663
2664 if (!client) {
2665 goto cleanup;
2666 }
2667
2668 for (i = 0; i < client->ch_endpt_count; ++i) {
2669 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2670 found = 1;
2671 break;
2672 }
2673 }
2674
2675cleanup:
2676 /* UNLOCK */
2677 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2678 return found;
2679}
2680
Michal Vasko056f53c2022-10-21 13:38:15 +02002681/**
2682 * @brief Create a connection for an endpoint.
2683 *
2684 * Client lock is expected to be held.
2685 *
2686 * @param[in] endpt Endpoint to use.
2687 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2688 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2689 * @param[in] ctx_cb_data Context callbacks data.
2690 * @param[out] session Created NC session.
2691 * @return NC_MSG values.
2692 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002693static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002694nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2695 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 +01002696{
Michal Vasko71090fc2016-05-24 16:37:28 +02002697 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002698 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002699 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002700 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002701 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002702
roman9ad4a8a2024-08-08 12:44:01 +02002703 sock = nc_sock_connect(endpt->src_addr, endpt->src_port, endpt->dst_addr, endpt->dst_port,
2704 NC_CH_CONNECT_TIMEOUT, &endpt->ka, &endpt->sock_pending, &ip_host);
Michal Vaskoc61c4492016-01-25 11:13:34 +01002705 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002706 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002707 }
2708
Michal Vasko93224072021-11-09 12:14:28 +01002709 /* acquire context */
2710 ctx = acquire_ctx_cb(ctx_cb_data);
2711 if (!ctx) {
2712 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2713 close(sock);
2714 free(ip_host);
2715 return NC_MSG_ERROR;
2716 }
2717
romanf578cd52023-10-19 09:47:40 +02002718 /* init ctx as needed */
2719 nc_server_init_cb_ctx(ctx);
2720
Michal Vasko93224072021-11-09 12:14:28 +01002721 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002722 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002723 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002724 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002725 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002726 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002727 (*session)->host = ip_host;
roman9ad4a8a2024-08-08 12:44:01 +02002728 (*session)->port = endpt->dst_port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002729
Michal Vaskob05053d2016-01-22 16:12:06 +01002730 /* sock gets assigned to session or closed */
roman506354a2024-04-11 09:37:22 +02002731 if (endpt->ti == NC_TI_SSH) {
romanf578cd52023-10-19 09:47:40 +02002732 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002733 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002734
Michal Vasko71090fc2016-05-24 16:37:28 +02002735 if (ret < 0) {
2736 msgtype = NC_MSG_ERROR;
2737 goto fail;
2738 } else if (!ret) {
2739 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002740 goto fail;
2741 }
roman506354a2024-04-11 09:37:22 +02002742 } else if (endpt->ti == NC_TI_TLS) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002743 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002744 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002745 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002746
Michal Vasko71090fc2016-05-24 16:37:28 +02002747 if (ret < 0) {
2748 msgtype = NC_MSG_ERROR;
2749 goto fail;
2750 } else if (!ret) {
2751 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002752 goto fail;
2753 }
roman423cc0d2023-11-24 11:29:47 +01002754 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002755 ERRINT;
2756 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002757 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002758 goto fail;
2759 }
2760
2761 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002762 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002763
2764 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002765 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002766 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002767 goto fail;
2768 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002769
Michal Vaskod8a74192023-02-06 15:51:50 +01002770 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002771 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002772 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002773 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002774 (*session)->status = NC_STATUS_RUNNING;
2775
Michal Vasko71090fc2016-05-24 16:37:28 +02002776 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002777
2778fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002779 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002780 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002781 if (ctx) {
2782 release_ctx_cb(ctx_cb_data);
2783 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002784 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002785}
2786
Michal Vasko6f865982023-11-21 12:10:42 +01002787/**
2788 * @brief Wait for any event after a NC session was established on a CH client.
2789 *
Michal Vasko6f865982023-11-21 12:10:42 +01002790 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002791 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002792 * @return 0 if session was terminated normally,
2793 * @return 1 if the CH client was removed,
2794 * @return -1 on error.
2795 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002796static int
roman8341e8b2023-11-23 16:12:42 +01002797nc_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 +02002798{
Michal Vasko6f865982023-11-21 12:10:42 +01002799 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002800 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002801 struct timespec ts;
2802 struct nc_ch_client *client;
2803
Michal Vasko2e6defd2016-10-07 15:48:15 +02002804 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002805 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002806
Michal Vaskofeccb312022-03-24 15:24:59 +01002807 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002808
Michal Vasko2e6defd2016-10-07 15:48:15 +02002809 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002810 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002811 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002812 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002813
2814 /* CH UNLOCK */
2815 pthread_mutex_unlock(&session->opts.server.ch_lock);
2816
Michal Vasko77d56d72022-09-07 10:30:48 +02002817 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002818 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002819 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002820 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002821 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002822
2823 do {
romanf578cd52023-10-19 09:47:40 +02002824 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002825
Michal Vasko0db3db52021-03-03 10:45:42 +01002826 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002827 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 +01002828 if (!r) {
2829 /* we were woken up, something probably happened */
2830 if (session->status != NC_STATUS_RUNNING) {
2831 break;
2832 }
2833 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002834 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002835 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002836 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002837 }
2838
Michal Vasko2e6defd2016-10-07 15:48:15 +02002839 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002840
Michal Vasko2e6defd2016-10-07 15:48:15 +02002841 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002842 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002843 if (!client) {
2844 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002845 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002846 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002847 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002848 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002849 }
2850
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002851 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002852 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002853 } else {
2854 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002855 }
2856
Michal Vaskod8a74192023-02-06 15:51:50 +01002857 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002858 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 +02002859 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002860 session->status = NC_STATUS_INVALID;
2861 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2862 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002863
2864 /* UNLOCK */
2865 nc_server_ch_client_unlock(client);
2866
2867 } while (session->status == NC_STATUS_RUNNING);
2868
Michal Vaskofeccb312022-03-24 15:24:59 +01002869 /* signal to nc_session_free() that CH thread is terminating */
2870 session->flags &= ~NC_SESSION_CH_THREAD;
2871 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002872
Michal Vasko27377422018-03-15 08:59:35 +01002873 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002874 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002875
Michal Vasko6f865982023-11-21 12:10:42 +01002876 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002877}
2878
romanf578cd52023-10-19 09:47:40 +02002879/**
2880 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2881 *
2882 * @param[in] session An established session.
2883 * @param[in] data Call Home thread's data.
2884 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2885 *
2886 * @return 0 if the thread should stop running, 1 if it should continue.
2887 */
2888static int
2889nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2890{
2891 struct timespec ts;
2892 int ret = 0, thread_running;
2893
2894 /* COND LOCK */
2895 pthread_mutex_lock(&data->cond_lock);
2896 /* get reconnect timeout in ms */
2897 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2898 while (!ret && data->thread_running) {
2899 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2900 }
2901
2902 thread_running = data->thread_running;
2903 /* COND UNLOCK */
2904 pthread_mutex_unlock(&data->cond_lock);
2905
2906 if (!thread_running) {
2907 /* thread is terminating */
2908 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2909 ret = 0;
2910 } else if (ret == ETIMEDOUT) {
2911 /* time to reconnect */
2912 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2913 ret = 1;
2914 } else if (ret) {
2915 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2916 ret = 0;
2917 }
2918
2919 return ret;
2920}
2921
2922/**
2923 * @brief Checks if a Call Home thread should terminate.
2924 *
2925 * Checks the shared boolean variable thread_running. This should be done everytime
2926 * before entering a critical section.
2927 *
2928 * @param[in] data Call Home thread's data.
2929 *
2930 * @return 0 if the thread should stop running, -1 if it can continue.
2931 */
2932static int
2933nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2934{
2935 int ret = -1;
2936
2937 /* COND LOCK */
2938 pthread_mutex_lock(&data->cond_lock);
2939 if (!data->thread_running) {
2940 /* thread should stop running */
2941 ret = 0;
2942 }
2943 /* COND UNLOCK */
2944 pthread_mutex_unlock(&data->cond_lock);
2945
2946 return ret;
2947}
2948
Michal Vasko6f865982023-11-21 12:10:42 +01002949/**
2950 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2951 *
2952 * @param[in] name Name of the CH client.
2953 * @return Pointer to the CH client.
2954 */
2955static struct nc_ch_client *
2956nc_server_ch_client_with_endpt_lock(const char *name)
2957{
2958 struct nc_ch_client *client;
2959
2960 while (1) {
2961 /* LOCK */
2962 client = nc_server_ch_client_lock(name);
2963 if (!client) {
2964 return NULL;
2965 }
2966 if (client->ch_endpt_count) {
2967 return client;
2968 }
2969 /* no endpoints defined yet */
2970
2971 /* UNLOCK */
2972 nc_server_ch_client_unlock(client);
2973
2974 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2975 }
2976
2977 return NULL;
2978}
2979
2980/**
2981 * @brief Call Home client management thread.
2982 *
2983 * @param[in] arg CH client thread argument.
2984 * @return NULL.
2985 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002986static void *
2987nc_ch_client_thread(void *arg)
2988{
Michal Vasko6f865982023-11-21 12:10:42 +01002989 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002990 NC_MSG_TYPE msgtype;
2991 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002992 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002993 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002994 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002995 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002996 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002997 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002998
2999 /* LOCK */
3000 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01003001 if (!client) {
3002 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
3003 goto cleanup;
3004 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003005
3006 cur_endpt = &client->ch_endpts[0];
3007 cur_endpt_name = strdup(cur_endpt->name);
3008
Michal Vasko6f865982023-11-21 12:10:42 +01003009 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02003010 if (!cur_attempts) {
3011 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
3012 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003013
Michal Vasko6f865982023-11-21 12:10:42 +01003014 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 +02003015 if (msgtype == NC_MSG_HELLO) {
3016 /* UNLOCK */
3017 nc_server_ch_client_unlock(client);
3018
romanf578cd52023-10-19 09:47:40 +02003019 if (!nc_server_ch_client_thread_is_running(data)) {
3020 /* thread should stop running */
3021 goto cleanup;
3022 }
3023
3024 /* run while the session is established */
3025 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01003026 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02003027 goto cleanup;
3028 }
roman8341e8b2023-11-23 16:12:42 +01003029 session = NULL;
romanf578cd52023-10-19 09:47:40 +02003030
roman8341e8b2023-11-23 16:12:42 +01003031 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02003032 if (!nc_server_ch_client_thread_is_running(data)) {
3033 /* thread should stop running */
3034 goto cleanup;
3035 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003036
3037 /* LOCK */
3038 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01003039 if (!client) {
3040 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
3041 goto cleanup;
3042 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003043
3044 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02003045 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02003046 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02003047 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01003048 /* anchored */
romanf578cd52023-10-19 09:47:40 +02003049 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01003050 } else {
3051 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02003052 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01003053 }
3054
Michal Vasko2e6defd2016-10-07 15:48:15 +02003055 /* UNLOCK */
3056 nc_server_ch_client_unlock(client);
3057
romanf578cd52023-10-19 09:47:40 +02003058 /* wait for the timeout to elapse, so we can try to reconnect */
3059 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
3060 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
3061 goto cleanup;
3062 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003063
3064 /* LOCK */
3065 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02003066 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003067 }
3068
3069 /* set next endpoint to try */
3070 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00003071 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02003072 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00003073 /* we keep the current one but due to unlock/lock we have to find it again */
3074 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
3075 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
3076 break;
3077 }
3078 }
3079 if (next_endpt_index >= client->ch_endpt_count) {
3080 /* endpoint was removed, start with the first one */
3081 next_endpt_index = 0;
3082 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02003083 } else {
3084 /* just get a random index */
3085 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00003086 }
3087
Michal Vasko2e6defd2016-10-07 15:48:15 +02003088 } else {
romanf578cd52023-10-19 09:47:40 +02003089 /* session was not created, wait a little bit and try again */
3090 max_wait = client->max_wait;
3091
Michal Vasko6bb116b2016-10-26 13:53:46 +02003092 /* UNLOCK */
3093 nc_server_ch_client_unlock(client);
3094
romanf578cd52023-10-19 09:47:40 +02003095 /* wait for max_wait seconds */
3096 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
3097 /* thread should stop running */
3098 goto cleanup;
3099 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02003100
Michal Vasko6bb116b2016-10-26 13:53:46 +02003101 /* LOCK */
3102 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02003103 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02003104
Michal Vasko2e6defd2016-10-07 15:48:15 +02003105 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02003106
3107 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00003108 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
3109 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02003110 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003111 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02003112 }
3113
Peter Feiged05f2252018-09-03 08:09:47 +00003114 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02003115 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02003116 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00003117 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02003118 cur_attempts = 0;
3119 } else if (cur_attempts == client->max_attempts) {
3120 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02003121 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02003122 data->client_name, cur_endpt_name, client->max_attempts);
3123
3124 /* clear a pending socket, if any */
3125 cur_endpt = &client->ch_endpts[next_endpt_index];
3126 if (cur_endpt->sock_pending > -1) {
3127 close(cur_endpt->sock_pending);
3128 cur_endpt->sock_pending = -1;
3129 }
3130
Peter Feiged05f2252018-09-03 08:09:47 +00003131 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02003132 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00003133 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003134 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02003135 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02003136 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003137 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02003138 cur_attempts = 0;
3139 } /* else we keep the current one */
3140 }
Peter Feiged05f2252018-09-03 08:09:47 +00003141
3142 cur_endpt = &client->ch_endpts[next_endpt_index];
3143 free(cur_endpt_name);
3144 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003145 }
Michal Vasko6f865982023-11-21 12:10:42 +01003146
romanf578cd52023-10-19 09:47:40 +02003147 /* UNLOCK if we break out of the loop */
3148 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003149
3150cleanup:
romanf578cd52023-10-19 09:47:40 +02003151 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01003152 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003153 free(data->client_name);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02003154 pthread_mutex_lock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01003155 pthread_cond_destroy(&data->cond);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02003156 pthread_mutex_unlock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01003157 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003158 free(data);
3159 return NULL;
3160}
3161
3162API int
Michal Vasko93224072021-11-09 12:14:28 +01003163nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02003164 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
3165 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01003166{
Michal Vasko6f865982023-11-21 12:10:42 +01003167 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003168 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01003169 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02003170 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003171
romanf578cd52023-10-19 09:47:40 +02003172 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
3173
romand82caf12024-03-05 14:21:39 +01003174 NC_CHECK_SRV_INIT_RET(-1);
3175
Michal Vasko6f865982023-11-21 12:10:42 +01003176 /* LOCK */
3177 ch_client = nc_server_ch_client_lock(client_name);
3178 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02003179 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003180 return -1;
3181 }
3182
Michal Vasko6f865982023-11-21 12:10:42 +01003183 /* create the thread argument */
3184 arg = calloc(1, sizeof *arg);
3185 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003186 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01003187 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01003188 arg->acquire_ctx_cb = acquire_ctx_cb;
3189 arg->release_ctx_cb = release_ctx_cb;
3190 arg->ctx_cb_data = ctx_cb_data;
3191 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02003192 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02003193 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02003194 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02003195
Michal Vasko6f865982023-11-21 12:10:42 +01003196 /* creating the thread */
3197 arg->thread_running = 1;
3198 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
3199 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
3200 rc = -1;
3201 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003202 }
Michal Vasko6f865982023-11-21 12:10:42 +01003203
Michal Vasko2e6defd2016-10-07 15:48:15 +02003204 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02003205 ch_client->tid = tid;
3206 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01003207 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003208
Michal Vasko6f865982023-11-21 12:10:42 +01003209cleanup:
3210 /* UNLOCK */
3211 nc_server_ch_client_unlock(ch_client);
3212
3213 if (arg) {
3214 free(arg->client_name);
3215 free(arg);
3216 }
3217 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02003218}
3219
romanf578cd52023-10-19 09:47:40 +02003220#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02003221
roman44efa322023-11-03 13:57:25 +01003222API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02003223nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02003224{
roman44efa322023-11-03 13:57:25 +01003225 struct timespec fail = {0};
3226
3227 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02003228
3229 if (session->side != NC_SERVER) {
3230 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01003231 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02003232 }
3233
Michal Vasko2e6defd2016-10-07 15:48:15 +02003234 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02003235}
Michal Vasko3486a7c2017-03-03 13:28:07 +01003236
3237API void
Michal Vasko71dbd772021-03-23 14:08:37 +01003238nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01003239{
3240 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003241 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003242 return;
3243 }
3244
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003245 /* NTF STATUS LOCK */
3246 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3247
Michal Vasko71dbd772021-03-23 14:08:37 +01003248 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003249
3250 /* NTF STATUS UNLOCK */
3251 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01003252}
3253
3254API void
3255nc_session_dec_notif_status(struct nc_session *session)
3256{
3257 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003258 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01003259 return;
3260 }
3261
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003262 /* NTF STATUS LOCK */
3263 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3264
Michal Vasko71dbd772021-03-23 14:08:37 +01003265 if (session->opts.server.ntf_status) {
3266 --session->opts.server.ntf_status;
3267 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003268
3269 /* NTF STATUS UNLOCK */
3270 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003271}
3272
3273API int
3274nc_session_get_notif_status(const struct nc_session *session)
3275{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003276 uint32_t ntf_status;
3277
Michal Vasko3486a7c2017-03-03 13:28:07 +01003278 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003279 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003280 return 0;
3281 }
3282
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003283 /* NTF STATUS LOCK */
3284 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3285
3286 ntf_status = session->opts.server.ntf_status;
3287
3288 /* NTF STATUS UNLOCK */
3289 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3290
3291 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003292}
roman3962bc82024-07-09 15:06:45 +02003293
3294#ifdef NC_ENABLED_SSH_TLS
3295
3296/**
3297 * @brief Get the XPath for the certificate expiration notification.
3298 *
3299 * @param[in] cp Keys of lists for the given certificate that are needed to create the XPath.
3300 * @return XPath for the certificate expiration notification or NULL on error.
3301 */
3302static char *
3303nc_server_notif_cert_exp_xpath_get(struct nc_cert_path_aux *cp)
3304{
3305 int rc;
3306 char *xpath = NULL, *tmp = NULL;
3307
3308 if (cp->ks_cert_name) {
3309 /* ietf-keystore */
3310 rc = asprintf(&xpath, "/ietf-keystore:keystore/asymmetric-keys/asymmetric-key[name='%s']/certificates/"
3311 "certificate[name='%s']/certificate-expiration/expiration-date", cp->ks_askey_name, cp->ks_cert_name);
3312 NC_CHECK_ERRMEM_RET(rc == -1, NULL);
3313 return xpath;
3314 } else if (cp->ts_cert_name) {
3315 /* ietf-truststore */
3316 rc = asprintf(&xpath, "/ietf-truststore:truststore/certificate-bags/certificate-bag[name='%s']/"
3317 "certificate[name='%s']/certificate-expiration/expiration-date", cp->ts_cbag_name, cp->ts_cert_name);
3318 NC_CHECK_ERRMEM_RET(rc == -1, NULL);
3319 return xpath;
3320 }
3321
3322 /* ietf-netconf-server */
3323 if (cp->ch_client_name) {
3324 /* call-home */
3325 rc = asprintf(&tmp, "/ietf-netconf-server:netconf-server/call-home/netconf-client[name='%s']/endpoints/"
3326 "endpoint[name='%s']/tls/tls-server-parameters", cp->ch_client_name, cp->endpt_name);
3327 } else {
3328 /* listen */
3329 rc = asprintf(&tmp, "/ietf-netconf-server:netconf-server/listen/endpoints/"
3330 "endpoint[name='%s']/tls/tls-server-parameters", cp->endpt_name);
3331 }
3332 NC_CHECK_ERRMEM_RET(rc == -1, NULL);
3333
3334 if (cp->ee_cert_name) {
3335 /* end entity */
3336 rc = asprintf(&xpath, "%s/client-authentication/ee-certs/inline-definition/certificate[name='%s']/"
3337 "certificate-expiration/expiration-date", tmp, cp->ee_cert_name);
3338 } else if (cp->ca_cert_name) {
3339 /* certificate authority */
3340 rc = asprintf(&xpath, "%s/client-authentication/ca-certs/inline-definition/certificate[name='%s']/"
3341 "certificate-expiration/expiration-date", tmp, cp->ca_cert_name);
3342 } else {
3343 /* server cert */
3344 rc = asprintf(&xpath, "%s/server-identity/certificate/inline-definition/certificate-expiration/expiration-date", tmp);
3345 }
3346 free(tmp);
3347 NC_CHECK_ERRMEM_RET(rc == -1, NULL);
3348
3349 return xpath;
3350}
3351
3352/**
3353 * @brief Add months, weeks, days and hours to a calendar time.
3354 *
3355 * @param[in] orig_time Original calendar time.
3356 * @param[in] add_time Months, weeks, days and hours to add.
3357 * @return Calendar time of the new time or -1 on error.
3358 */
3359static time_t
3360nc_server_notif_cert_exp_time_add(time_t orig_time, struct nc_cert_exp_time *add_time)
3361{
3362 struct tm *tm;
3363 struct tm tm_aux;
3364
3365 tm = localtime_r(&orig_time, &tm_aux);
3366 if (!tm) {
3367 ERR(NULL, "Failed to get localtime (%s).", strerror(errno));
3368 return -1;
3369 }
3370
3371 tm->tm_mon += add_time->months;
3372 tm->tm_mday += 7 * add_time->weeks;
3373 tm->tm_mday += add_time->days;
3374 tm->tm_hour += add_time->hours;
3375
3376 return mktime(tm);
3377}
3378
3379/**
3380 * @brief Subtract months, weeks, days and hours from a calendar time.
3381 *
3382 * @param[in] orig_time Original calendar time.
3383 * @param[in] sub_time Months, weeks, days and hours to subtract.
3384 * @return Calendar time of the new time or -1 on error.
3385 */
3386static time_t
3387nc_server_notif_cert_exp_time_sub(time_t orig_time, struct nc_cert_exp_time *sub_time)
3388{
3389 struct tm *tm;
3390 struct tm tm_aux;
3391
3392 tm = localtime_r(&orig_time, &tm_aux);
3393 if (!tm) {
3394 ERR(NULL, "Failed to get localtime (%s).", strerror(errno));
3395 return -1;
3396 }
3397
3398 tm->tm_mon -= sub_time->months;
3399 tm->tm_mday -= 7 * sub_time->weeks;
3400 tm->tm_mday -= sub_time->days;
3401 tm->tm_hour -= sub_time->hours;
3402
3403 return mktime(tm);
3404}
3405
3406/**
3407 * @brief Get the next notification time for the certificate expiration.
3408 *
3409 * @param[in] intervals Certificate expiration time intervals.
3410 * @param[in] interval_count Interval count.
3411 * @param[in,out] exp Expiration date structure.
3412 * @return Calendar time of the next notification or -1 on error.
3413 */
3414static time_t
3415nc_server_notif_cert_exp_next_notif_time_get(struct nc_interval *intervals, int interval_count, struct nc_cert_expiration *exp)
3416{
3417 time_t new_notif_time, now;
3418 double diff;
3419 struct nc_cert_exp_time day_period = {.days = 1};
3420
3421 now = time(NULL);
3422
3423 /* check if the certificate already expired */
3424 diff = difftime(exp->expiration_time, now);
3425 if (diff < 0) {
3426 /* it did, so the next notif shall happen on the next day regardless of set intervals */
3427 return nc_server_notif_cert_exp_time_add(exp->notif_time, &day_period);
3428 }
3429
3430 /* otherwise just add the current period and check for overflow into the next interval */
3431 new_notif_time = nc_server_notif_cert_exp_time_add(exp->notif_time, &intervals[exp->current_interval].period);
3432 if (new_notif_time == -1) {
3433 return -1;
3434 }
3435
3436 if (exp->current_interval == (interval_count - 1)) {
3437 /* we are in the last interval, so we cant overflow */
3438 return new_notif_time;
3439 }
3440
3441 diff = difftime(exp->starts_of_intervals[exp->current_interval + 1], new_notif_time);
3442 if (diff > 0) {
3443 /* no overflow */
3444 return new_notif_time;
3445 } else {
3446 /* overflowed, move to the next interval */
3447 ++exp->current_interval;
3448 return exp->starts_of_intervals[exp->current_interval];
3449 }
3450}
3451
3452/**
3453 * @brief Initialize the start times of the intervals for the specific certificate expiration.
3454 *
3455 * @param[in] intervals Certificate expiration time intervals.
3456 * @param[in] interval_count Interval count.
3457 * @param[in,out] exp Certificate expiration structure.
3458 * @return 0 on success, 1 on error.
3459 */
3460static int
3461nc_server_notif_cert_exp_init_intervals(struct nc_interval *intervals, int interval_count, struct nc_cert_expiration *exp)
3462{
3463 int i;
3464
3465 exp->starts_of_intervals = malloc(interval_count * sizeof *exp->starts_of_intervals);
3466 NC_CHECK_ERRMEM_RET(!exp->starts_of_intervals, 1);
3467
3468 /* find the start time of each interval */
3469 for (i = 0; i < interval_count; i++) {
3470 exp->starts_of_intervals[i] = nc_server_notif_cert_exp_time_sub(exp->expiration_time, &intervals[i].anchor);
3471 if (exp->starts_of_intervals[i] == -1) {
3472 return 1;
3473 }
3474 }
3475
3476 return 0;
3477}
3478
3479/**
3480 * @brief Get the first notification time and the given interval for the certificate expiration.
3481 *
3482 * @param[in] intervals Certificate expiration time intervals.
3483 * @param[in] interval_count Interval count.
3484 * @param[in,out] exp Certificate expiration structure.
3485 * @return 0 on success.
3486 */
3487static int
3488nc_server_notif_cert_exp_first_notif_time_get(struct nc_interval *intervals, int interval_count, struct nc_cert_expiration *exp)
3489{
3490 int i;
3491 time_t now, notif_time;
3492 double diff;
3493
3494 now = time(NULL);
3495
3496 /* check if the start of the first interval is in the future, since they are sorted by calendar time (ascending) */
3497 diff = difftime(exp->starts_of_intervals[0], now);
3498 if (diff > 0) {
3499 /* it is, so the first notif shall happen at the start of the first interval */
3500 exp->notif_time = exp->starts_of_intervals[0];
3501 exp->current_interval = 0;
3502 return 0;
3503 }
3504
3505 /* check if the certificate already expired */
3506 diff = difftime(exp->expiration_time, now);
3507 if (diff < 0) {
3508 /* it did, so the first notif shall happen immediately */
3509 exp->notif_time = now;
3510 exp->current_interval = interval_count - 1;
3511 return 0;
3512 }
3513
3514 /* otherwise we have to find the correct interval */
3515 for (i = 0; i < interval_count - 1; i++) {
3516 if ((difftime(now, exp->starts_of_intervals[i]) >= 0) && (difftime(now, exp->starts_of_intervals[i + 1]) < 0)) {
3517 /* found it (now is at or after i, but before i + 1) */
3518 break;
3519 }
3520 }
3521
3522 /* now we have to find the exact notification time based on the interval and its period */
3523 notif_time = exp->starts_of_intervals[i];
3524 while (difftime(notif_time, now) < 0) {
3525 /* the notif_time is still in the past, so we add the given period and check for overflow into the next interval */
3526 notif_time = nc_server_notif_cert_exp_time_add(notif_time, &intervals[i].period);
3527 if (notif_time == -1) {
3528 return 1;
3529 }
3530
3531 if ((i != (interval_count - 1)) && (difftime(notif_time, exp->starts_of_intervals[i + 1]) >= 0)) {
3532 /* overflowed into the next interval */
3533 notif_time = exp->starts_of_intervals[i + 1];
3534 ++i;
3535 break;
3536 }
3537 }
3538
3539 exp->notif_time = notif_time;
3540 exp->current_interval = i;
3541 return 0;
3542}
3543
3544/**
3545 * @brief Initialize and append the certificate expiration date to an array.
3546 *
3547 * @param[in] cert_data Base64 encoded certificate data.
3548 * @param[in] cp Keys of lists required to create the XPath to the certificate expiration date.
3549 * @param[in] intervals Certificate expiration time intervals.
3550 * @param[in] interval_count Interval count.
3551 * @param[out] exp_dates Expiration dates.
3552 * @param[out] exp_date_count Expiration date count.
3553 * @return 0 on success, 1 on error.
3554 */
3555static int
3556nc_server_notif_cert_exp_date_append(const char *cert_data, struct nc_cert_path_aux *cp,
3557 struct nc_interval *intervals, int interval_count, struct nc_cert_expiration **exp_dates, int *exp_date_count)
3558{
3559 int ret = 0;
3560 void *cert = NULL;
3561 time_t exp_time;
3562
3563 cert = nc_base64der_to_cert(cert_data);
3564 if (!cert) {
3565 ret = 1;
3566 goto cleanup;
3567 }
3568
3569 /* get expiration date */
3570 exp_time = nc_tls_get_cert_exp_time_wrap(cert);
3571 if (exp_time == -1) {
3572 ret = 1;
3573 goto cleanup;
3574 }
3575
3576 *exp_dates = nc_realloc(*exp_dates, (*exp_date_count + 1) * sizeof **exp_dates);
3577 NC_CHECK_ERRMEM_GOTO(!*exp_dates, ret = 1, cleanup);
3578
3579 (*exp_dates)[*exp_date_count].expiration_time = exp_time;
3580
3581 /* init the time intervals for this specific cert */
3582 ret = nc_server_notif_cert_exp_init_intervals(intervals, interval_count, &(*exp_dates)[*exp_date_count]);
3583 if (ret) {
3584 goto cleanup;
3585 }
3586
3587 /* get the time of the first notif */
3588 ret = nc_server_notif_cert_exp_first_notif_time_get(intervals, interval_count, &(*exp_dates)[*exp_date_count]);
3589 if (ret) {
3590 goto cleanup;
3591 }
3592
3593 /* get the XPath to this specific cert */
3594 (*exp_dates)[*exp_date_count].xpath = nc_server_notif_cert_exp_xpath_get(cp);
3595 if (!(*exp_dates)[*exp_date_count].xpath) {
3596 ret = 1;
3597 goto cleanup;
3598 }
3599
3600 ++(*exp_date_count);
3601
3602cleanup:
3603 nc_tls_cert_destroy_wrap(cert);
3604 return ret;
3605}
3606
3607/**
3608 * @brief Get the certificate expiration dates for all the certificates in the given endpoint.
3609 *
3610 * @param[in] ch_client_name Call Home client name.
3611 * @param[in] endpt_name Endpoint name.
3612 * @param[in] opts TLS server options.
3613 * @param[in] intervals Certificate expiration time intervals.
3614 * @param[in] interval_count Interval count.
3615 * @param[out] exp_dates Expiration dates.
3616 * @param[out] exp_date_count Expiration date count.
3617 * @return 0 on success, 1 on error.
3618 */
3619static int
3620nc_server_notif_cert_exp_dates_endpt_get(const char *ch_client_name, const char *endpt_name, struct nc_server_tls_opts *opts,
3621 struct nc_interval *intervals, int interval_count, struct nc_cert_expiration **exp_dates, int *exp_date_count)
3622{
3623 int ret = 0, i;
3624 struct nc_certificate *certs;
3625 uint16_t ncerts;
3626 struct nc_cert_path_aux cp = {0};
3627
3628 /* append server cert first */
3629 if (opts->store == NC_STORE_LOCAL) {
3630 NC_CERT_EXP_UPDATE_CERT_PATH(&cp, ch_client_name, endpt_name, NULL, NULL, NULL, NULL, NULL, NULL);
3631 ret = nc_server_notif_cert_exp_date_append(opts->cert_data, &cp, intervals, interval_count, exp_dates, exp_date_count);
3632 if (ret) {
3633 goto cleanup;
3634 }
3635 }
3636
3637 /* append CA certs */
3638 if (opts->ca_certs.store == NC_STORE_LOCAL) {
3639 certs = opts->ca_certs.certs;
3640 ncerts = opts->ca_certs.cert_count;
3641
3642 for (i = 0; i < ncerts; i++) {
3643 NC_CERT_EXP_UPDATE_CERT_PATH(&cp, ch_client_name, endpt_name, certs[i].name, NULL, NULL, NULL, NULL, NULL);
3644 ret = nc_server_notif_cert_exp_date_append(certs[i].data, &cp, intervals, interval_count, exp_dates, exp_date_count);
3645 if (ret) {
3646 goto cleanup;
3647 }
3648 }
3649 }
3650
3651 /* append end entity certs */
3652 if (opts->ee_certs.store == NC_STORE_LOCAL) {
3653 certs = opts->ee_certs.certs;
3654 ncerts = opts->ee_certs.cert_count;
3655
3656 for (i = 0; i < ncerts; i++) {
3657 NC_CERT_EXP_UPDATE_CERT_PATH(&cp, ch_client_name, endpt_name, NULL, certs[i].name, NULL, NULL, NULL, NULL);
3658 ret = nc_server_notif_cert_exp_date_append(certs[i].data, &cp, intervals, interval_count, exp_dates, exp_date_count);
3659 if (ret) {
3660 goto cleanup;
3661 }
3662 }
3663 }
3664
3665cleanup:
3666 return ret;
3667}
3668
3669/**
3670 * @brief Get the certificate expiration dates for all the certificates in the server configuration.
3671 *
3672 * @param[in] intervals Certificate expiration time intervals.
3673 * @param[in] interval_count Interval count.
3674 * @param[out] exp_dates Expiration dates.
3675 * @param[out] exp_date_count Expiration date count.
3676 * @return 0 on success, 1 on error.
3677 */
3678static int
3679nc_server_notif_cert_exp_dates_get(struct nc_interval *intervals, int interval_count, struct nc_cert_expiration **exp_dates, int *exp_date_count)
3680{
3681 int ret = 0;
3682 uint16_t i, j;
3683 struct nc_keystore *ks = &server_opts.keystore;
3684 struct nc_truststore *ts = &server_opts.truststore;
3685 struct nc_cert_path_aux cp = {0};
3686
3687 NC_CHECK_ARG_RET(NULL, intervals, interval_count, exp_dates, exp_date_count, 1);
3688
3689 *exp_dates = NULL;
3690 *exp_date_count = 0;
3691
3692 /* CONFIG LOCK */
3693 pthread_rwlock_rdlock(&server_opts.config_lock);
3694
3695 /* first go through listen certs */
3696 for (i = 0; i < server_opts.endpt_count; ++i) {
3697 if (server_opts.endpts[i].ti == NC_TI_TLS) {
3698 ret = nc_server_notif_cert_exp_dates_endpt_get(NULL, server_opts.endpts[i].name,
3699 server_opts.endpts[i].opts.tls, intervals, interval_count, exp_dates, exp_date_count);
3700 if (ret) {
3701 goto cleanup;
3702 }
3703 }
3704 }
3705
3706 /* then go through all the ch clients and their endpts */
3707 /* CH CLIENT LOCK */
3708 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
3709 for (i = 0; i < server_opts.ch_client_count; ++i) {
3710 /* CH LOCK */
3711 pthread_mutex_lock(&server_opts.ch_clients[i].lock);
3712 for (j = 0; j < server_opts.ch_clients[i].ch_endpt_count; ++j) {
3713 if (server_opts.ch_clients[i].ch_endpts[j].ti == NC_TI_TLS) {
3714 ret = nc_server_notif_cert_exp_dates_endpt_get(server_opts.ch_clients[i].name,
3715 server_opts.ch_clients[i].ch_endpts[j].name, server_opts.ch_clients[i].ch_endpts[j].opts.tls,
3716 intervals, interval_count, exp_dates, exp_date_count);
3717 if (ret) {
3718 /* CH UNLOCK */
3719 pthread_mutex_unlock(&server_opts.ch_clients[i].lock);
3720 /* CH CLIENT UNLOCK */
3721 pthread_rwlock_unlock(&server_opts.ch_client_lock);
3722 goto cleanup;
3723 }
3724 }
3725 }
3726 /* CH UNLOCK */
3727 pthread_mutex_unlock(&server_opts.ch_clients[i].lock);
3728 }
3729 /* CH CLIENT UNLOCK */
3730 pthread_rwlock_unlock(&server_opts.ch_client_lock);
3731
3732 /* keystore certs */
3733 for (i = 0; i < ks->asym_key_count; i++) {
3734 for (j = 0; j < ks->asym_keys[i].cert_count; j++) {
3735 NC_CERT_EXP_UPDATE_CERT_PATH(&cp, NULL, NULL, NULL, NULL, ks->asym_keys[i].name, ks->asym_keys[i].certs[j].name, NULL, NULL);
3736 ret = nc_server_notif_cert_exp_date_append(ks->asym_keys[i].certs[j].data, &cp, intervals, interval_count, exp_dates, exp_date_count);
3737 if (ret) {
3738 goto cleanup;
3739 }
3740 }
3741 }
3742
3743 /* truststore certs */
3744 for (i = 0; i < ts->cert_bag_count; i++) {
3745 for (j = 0; j < ts->cert_bags[i].cert_count; j++) {
3746 NC_CERT_EXP_UPDATE_CERT_PATH(&cp, NULL, NULL, NULL, NULL, NULL, NULL, ts->cert_bags[i].name, ts->cert_bags[i].certs[j].name);
3747 ret = nc_server_notif_cert_exp_date_append(ts->cert_bags[i].certs[j].data, &cp, intervals, interval_count, exp_dates, exp_date_count);
3748 if (ret) {
3749 goto cleanup;
3750 }
3751 }
3752 }
3753
3754cleanup:
3755 /* CONFIG UNLOCK */
3756 pthread_rwlock_unlock(&server_opts.config_lock);
3757 return ret;
3758}
3759
3760/**
3761 * @brief Get the time when the certificate expiration notification thread should wake up.
3762 *
3763 * @param[in] exp_dates Expiration dates.
3764 * @param[in] exp_date_count Expiration date count.
3765 * @param[out] next Certificate that the notification thread should notify about.
3766 * @return 0 if the thread should wake up immediately, otherwise a calendar time in the future.
3767 */
3768static time_t
3769nc_server_notif_cert_exp_wakeup_time_get(struct nc_cert_expiration *exp_dates, int exp_date_count, struct nc_cert_expiration **next)
3770{
3771 time_t min_time = LONG_MAX;
3772 int i;
3773 double diff;
Michal Vasko73f63af2024-08-15 11:56:07 +02003774 time_t now, wakeup_time = 0;
roman3962bc82024-07-09 15:06:45 +02003775
3776 *next = NULL;
3777
3778 now = time(NULL);
3779 if (!exp_date_count) {
3780 /* no certificates, set a "very long timeout" for the thread, it shall wake up on the change of config */
3781 wakeup_time = now + 365 * 24 * 60 * 60;
3782 return wakeup_time;
3783 }
3784
3785 /* find the minimum wait time */
3786 for (i = 0; i < exp_date_count; i++) {
3787 diff = difftime(exp_dates[i].notif_time, now);
3788 if (diff <= 0) {
3789 /* already expired, notify immediately */
3790 *next = &exp_dates[i];
3791 return 0;
3792 }
3793
3794 if (diff < min_time) {
3795 min_time = diff;
3796 wakeup_time = exp_dates[i].notif_time;
3797 *next = &exp_dates[i];
3798 }
3799 }
3800
3801 return wakeup_time;
3802}
3803
3804/**
3805 * @brief Destroy the certificate expiration notification data.
3806 *
3807 * @param[in] exp_dates Expiration dates.
3808 * @param[in] exp_date_count Expiration date count.
3809 */
3810static void
3811nc_server_notif_cert_exp_dates_destroy(struct nc_cert_expiration *exp_dates, int exp_date_count)
3812{
3813 int i;
3814
3815 for (i = 0; i < exp_date_count; i++) {
3816 free(exp_dates[i].starts_of_intervals);
3817 free(exp_dates[i].xpath);
3818 }
3819 free(exp_dates);
3820}
3821
3822/**
3823 * @brief Check if the certificate expiration notification thread is running.
3824 *
3825 * @return 1 if the thread is running, 0 otherwise.
3826 */
3827static int
3828nc_server_notif_cert_exp_thread_is_running()
3829{
3830 int ret = 0;
3831
3832 /* LOCK */
roman2c105342024-07-10 16:09:45 +02003833 pthread_mutex_lock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003834
roman2c105342024-07-10 16:09:45 +02003835 if (server_opts.cert_exp_notif.thread_running) {
roman3962bc82024-07-09 15:06:45 +02003836 ret = 1;
3837 }
3838
3839 /* UNLOCK */
roman2c105342024-07-10 16:09:45 +02003840 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003841
3842 return ret;
3843}
3844
3845/**
3846 * @brief Get the certificate expiration notification time intervals either from the config or the default ones.
3847 *
3848 * @param[in] default_intervals Default intervals.
3849 * @param[in] default_interval_count Default interval count.
3850 * @param[out] intervals Actual intervals to be used.
3851 * @param[out] interval_count Used interval count.
3852 */
3853static void
3854nc_server_notif_cert_exp_intervals_get(struct nc_interval *default_intervals, int default_interval_count,
3855 struct nc_interval **intervals, int *interval_count)
3856{
3857 /* LOCK */
roman2c105342024-07-10 16:09:45 +02003858 pthread_mutex_lock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003859
roman2c105342024-07-10 16:09:45 +02003860 if (!server_opts.cert_exp_notif.intervals) {
roman3962bc82024-07-09 15:06:45 +02003861 /* using the default intervals */
3862 *intervals = default_intervals;
3863 *interval_count = default_interval_count;
3864 } else {
3865 /* using configured intervals */
roman2c105342024-07-10 16:09:45 +02003866 *intervals = server_opts.cert_exp_notif.intervals;
3867 *interval_count = server_opts.cert_exp_notif.interval_count;
roman3962bc82024-07-09 15:06:45 +02003868 }
3869
3870 /* UNLOCK */
roman2c105342024-07-10 16:09:45 +02003871 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003872}
3873
3874/**
3875 * @brief Certificate expiration notification thread.
3876 *
3877 * @param[in] arg Thread argument.
3878 *
3879 * @return NULL.
3880 */
3881static void *
3882nc_server_notif_cert_exp_thread(void *arg)
3883{
3884 int r = 0, exp_date_count = 0;
3885 struct nc_cert_exp_notif_thread_arg *targ = arg;
3886 struct nc_cert_expiration *exp_dates = NULL, *curr_cert = NULL;
3887 struct timespec wakeup_time = {0};
3888 char *exp_time = NULL;
3889 struct nc_interval default_intervals[3] = {
3890 {.anchor = {.months = 3}, .period = {.months = 1}},
3891 {.anchor = {.weeks = 2}, .period = {.weeks = 1}},
3892 {.anchor = {.days = 7}, .period = {.days = 1}}
3893 };
3894 struct nc_interval *intervals;
3895 int interval_count;
3896
3897 /* get certificate expiration time intervals */
3898 nc_server_notif_cert_exp_intervals_get(default_intervals, 3, &intervals, &interval_count);
3899
3900 /* get the expiration dates */
3901 r = nc_server_notif_cert_exp_dates_get(intervals, interval_count, &exp_dates, &exp_date_count);
3902 if (r) {
3903 goto cleanup;
3904 }
3905
3906 while (nc_server_notif_cert_exp_thread_is_running()) {
3907 /* get the next notification time and the cert to send it for */
3908 wakeup_time.tv_sec = nc_server_notif_cert_exp_wakeup_time_get(exp_dates, exp_date_count, &curr_cert);
3909
3910 /* sleep until the next notification time or until the thread is woken up */
roman2c105342024-07-10 16:09:45 +02003911 pthread_mutex_lock(&server_opts.cert_exp_notif.lock);
3912 r = pthread_cond_clockwait(&server_opts.cert_exp_notif.cond,
3913 &server_opts.cert_exp_notif.lock, CLOCK_REALTIME, &wakeup_time);
3914 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003915
3916 if (!r) {
3917 /* we were woken up */
3918 if (!nc_server_notif_cert_exp_thread_is_running()) {
3919 /* end the thread */
3920 break;
3921 }
3922
3923 /* config changed, reload the certificates and intervals */
3924 nc_server_notif_cert_exp_dates_destroy(exp_dates, exp_date_count);
3925
3926 nc_server_notif_cert_exp_intervals_get(default_intervals, 3, &intervals, &interval_count);
3927
3928 r = nc_server_notif_cert_exp_dates_get(intervals, interval_count, &exp_dates, &exp_date_count);
3929 if (r) {
3930 break;
3931 }
3932 } else if (r == ETIMEDOUT) {
3933 /* time to send the notification */
3934 if (!curr_cert) {
3935 /* no certificates to notify about */
3936 continue;
3937 }
3938
3939 /* convert the expiration time to string */
3940 r = ly_time_time2str(curr_cert->expiration_time, NULL, &exp_time);
3941 if (r) {
3942 break;
3943 }
3944
3945 /* call the callback */
3946 targ->clb(exp_time, curr_cert->xpath, targ->clb_data);
3947 free(exp_time);
3948
3949 /* update the next notification time */
3950 curr_cert->notif_time = nc_server_notif_cert_exp_next_notif_time_get(intervals, interval_count, curr_cert);
3951 if (curr_cert->notif_time == -1) {
3952 break;
3953 }
3954 } else {
3955 ERR(NULL, "Pthread condition timedwait failed (%s).", strerror(r));
3956 break;
3957 }
3958 }
3959
3960cleanup:
3961 VRB(NULL, "Certificate expiration notification thread exit.");
3962 if (targ->clb_free_data) {
3963 targ->clb_free_data(targ->clb_data);
3964 }
3965 nc_server_notif_cert_exp_dates_destroy(exp_dates, exp_date_count);
3966 free(targ);
3967 return NULL;
3968}
3969
3970API int
3971nc_server_notif_cert_expiration_thread_start(nc_cert_exp_notif_clb cert_exp_notif_clb,
3972 void *user_data, void (*free_data)(void *))
3973{
3974 int r, ret = 0;
3975 pthread_t tid;
3976 struct nc_cert_exp_notif_thread_arg *arg;
3977
3978 NC_CHECK_ARG_RET(NULL, cert_exp_notif_clb, 1);
3979
3980 /* set the user callback and its data */
3981 arg = malloc(sizeof *arg);
3982 NC_CHECK_ERRMEM_RET(!arg, 1);
3983 arg->clb = cert_exp_notif_clb;
3984 arg->clb_data = user_data;
3985 arg->clb_free_data = free_data;
3986
3987 /* LOCK */
roman2c105342024-07-10 16:09:45 +02003988 pthread_mutex_lock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02003989
3990 /* check if the thread is already running */
roman2c105342024-07-10 16:09:45 +02003991 if (server_opts.cert_exp_notif.thread_running) {
roman3962bc82024-07-09 15:06:45 +02003992 ERR(NULL, "Certificate expiration notification thread is already running.");
3993 ret = 1;
3994 goto cleanup;
3995 } else {
roman2c105342024-07-10 16:09:45 +02003996 server_opts.cert_exp_notif.thread_running = 1;
roman3962bc82024-07-09 15:06:45 +02003997 }
3998
3999 if ((r = pthread_create(&tid, NULL, nc_server_notif_cert_exp_thread, arg))) {
4000 ERR(NULL, "Creating the certificate expiration notification thread failed (%s).", strerror(r));
4001 ret = 1;
4002 goto cleanup;
4003 }
4004
roman2c105342024-07-10 16:09:45 +02004005 server_opts.cert_exp_notif.tid = tid;
roman3962bc82024-07-09 15:06:45 +02004006
4007cleanup:
4008 /* UNLOCK */
roman2c105342024-07-10 16:09:45 +02004009 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02004010 if (ret) {
4011 free(arg);
4012 }
4013 return ret;
4014}
4015
4016API void
4017nc_server_notif_cert_expiration_thread_stop(int wait)
4018{
4019 int r;
romanb6c64d32024-07-10 16:11:02 +02004020 pthread_t tid;
roman3962bc82024-07-09 15:06:45 +02004021
4022 /* LOCK */
roman2c105342024-07-10 16:09:45 +02004023 pthread_mutex_lock(&server_opts.cert_exp_notif.lock);
romanb6c64d32024-07-10 16:11:02 +02004024 tid = server_opts.cert_exp_notif.tid;
4025
roman2c105342024-07-10 16:09:45 +02004026 if (server_opts.cert_exp_notif.thread_running) {
romanb6c64d32024-07-10 16:11:02 +02004027 /* set the tid and running flag to 0, signal the thread and unlock its mutex */
roman2c105342024-07-10 16:09:45 +02004028 server_opts.cert_exp_notif.thread_running = 0;
romanb6c64d32024-07-10 16:11:02 +02004029 server_opts.cert_exp_notif.tid = 0;
roman2c105342024-07-10 16:09:45 +02004030 pthread_cond_signal(&server_opts.cert_exp_notif.cond);
roman3962bc82024-07-09 15:06:45 +02004031
4032 /* UNLOCK */
roman2c105342024-07-10 16:09:45 +02004033 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02004034 if (wait) {
romanb6c64d32024-07-10 16:11:02 +02004035 r = pthread_join(tid, NULL);
roman87350362024-08-07 09:23:29 +02004036 } else {
4037 r = pthread_detach(tid);
4038 }
4039 if (r) {
4040 ERR(NULL, "Stopping the certificate expiration notification thread failed (%s).", strerror(r));
roman3962bc82024-07-09 15:06:45 +02004041 }
4042 } else {
4043 /* thread is not running */
4044 /* UNLOCK */
roman2c105342024-07-10 16:09:45 +02004045 pthread_mutex_unlock(&server_opts.cert_exp_notif.lock);
roman3962bc82024-07-09 15:06:45 +02004046 }
4047}
4048
4049#endif /* NC_ENABLED_SSH_TLS */