blob: 94dd5d502ad8cff6ffbe8301596a33af4dde4785 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file session_server.c
3 * @author Michal Vasko <mvasko@cesnet.cz>
4 * @brief libnetconf2 server session manipulation functions
Michal Vasko086311b2016-01-08 09:53:11 +01005 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01006 * @copyright
Michal Vasko63b92d62024-04-29 10:04:56 +02007 * Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
Michal Vasko086311b2016-01-08 09:53:11 +01008 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +01009 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010012 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010013 * https://opensource.org/licenses/BSD-3-Clause
Michal Vasko086311b2016-01-08 09:53:11 +010014 */
apropp-molex4e903c32020-04-20 03:06:58 -040015#define _QNX_SOURCE /* getpeereid */
Michal Vasko63b92d62024-04-29 10:04:56 +020016#define _GNU_SOURCE /* threads, SO_PEERCRED */
Michal Vasko086311b2016-01-08 09:53:11 +010017
Michal Vaskob83a3fa2021-05-26 09:53:42 +020018#include <arpa/inet.h>
Michal Vasko77e83572022-07-21 15:31:15 +020019#include <assert.h>
Michal Vasko086311b2016-01-08 09:53:11 +010020#include <errno.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020021#include <fcntl.h>
Olivier Matzac7fa2f2018-10-11 10:02:04 +020022#include <netinet/in.h>
23#include <netinet/tcp.h>
Michal Vaskob48aa812016-01-18 14:13:09 +010024#include <pthread.h>
Olivier Matzac7fa2f2018-10-11 10:02:04 +020025#include <pwd.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020026#include <stdint.h>
27#include <stdlib.h>
28#include <string.h>
29#include <sys/socket.h>
romanf578cd52023-10-19 09:47:40 +020030#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020031#include <sys/types.h>
32#include <sys/un.h>
33#include <time.h>
34#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010035
romanf578cd52023-10-19 09:47:40 +020036#ifdef NC_ENABLED_SSH_TLS
37#include <curl/curl.h>
Michal Vasko5788c802024-05-03 16:14:40 +020038#include <libssh/libssh.h>
romanf578cd52023-10-19 09:47:40 +020039#endif
40
Michal Vasko7a20d2e2021-05-19 16:40:23 +020041#include "compat.h"
romanf578cd52023-10-19 09:47:40 +020042#include "config.h"
43#include "log_p.h"
44#include "messages_p.h"
45#include "messages_server.h"
46#include "server_config_p.h"
47#include "session.h"
48#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010049#include "session_server.h"
Michal Vasko0bdf70b2019-06-24 19:20:20 +020050#include "session_server_ch.h"
Michal Vasko086311b2016-01-08 09:53:11 +010051
Michal Vaskob48aa812016-01-18 14:13:09 +010052struct nc_server_opts server_opts = {
romanf578cd52023-10-19 09:47:40 +020053 .config_lock = PTHREAD_RWLOCK_INITIALIZER,
54 .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER,
Michal Vaskocf898172024-01-15 15:04:28 +010055 .idle_timeout = 180, /**< default idle timeout (not in config for UNIX socket) */
Michal Vaskob48aa812016-01-18 14:13:09 +010056};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010057
fanchanghu966f2de2016-07-21 02:28:57 -040058static nc_rpc_clb global_rpc_clb = NULL;
59
roman423cc0d2023-11-24 11:29:47 +010060#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010061/**
62 * @brief Lock CH client structures for reading and lock the specific client.
63 *
64 * @param[in] name Name of the CH client.
65 * @return CH client, NULL if not found.
66 */
67static struct nc_ch_client *
68nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010069{
70 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020071 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020072
Michal Vasko6f865982023-11-21 12:10:42 +010073 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020074
Michal Vasko2e6defd2016-10-07 15:48:15 +020075 /* READ LOCK */
76 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
77
78 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020079 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020080 client = &server_opts.ch_clients[i];
81 break;
82 }
83 }
84
85 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020086 /* READ UNLOCK */
87 pthread_rwlock_unlock(&server_opts.ch_client_lock);
88 } else {
89 /* CH CLIENT LOCK */
90 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020091 }
92
Michal Vasko6f865982023-11-21 12:10:42 +010093 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020094}
95
Michal Vasko6f865982023-11-21 12:10:42 +010096/**
97 * @brief Unlock CH client strcutures and the specific client.
98 *
99 * @param[in] endpt Locked CH client structure.
100 */
101static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200102nc_server_ch_client_unlock(struct nc_ch_client *client)
103{
104 /* CH CLIENT UNLOCK */
105 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100106
107 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200108 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100109}
Michal Vasko086311b2016-01-08 09:53:11 +0100110
roman423cc0d2023-11-24 11:29:47 +0100111#endif /* NC_ENABLED_SSH_TLS */
112
roman78df0fa2023-11-02 10:33:57 +0100113int
114nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
115{
116 uint16_t i;
117
118 for (i = 0; i < server_opts.endpt_count; i++) {
119 if (!strcmp(name, server_opts.endpts[i].name)) {
120 *endpt = &server_opts.endpts[i];
121 return 0;
122 }
123 }
124
125 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
126 return 1;
127}
128
Michal Vasko1a38c862016-01-15 15:50:07 +0100129API void
130nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
131{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200132 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200133 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200134 return;
135 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200136 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100137 return;
138 }
139
Michal Vasko142cfea2017-08-07 10:12:11 +0200140 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
141 session->killed_by = 0;
142 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100143 session->term_reason = reason;
144}
145
Michal Vasko142cfea2017-08-07 10:12:11 +0200146API void
147nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
148{
149 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200150 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200151 return;
152 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200153 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200154 return;
155 }
156
157 session->killed_by = sid;
158}
159
160API void
161nc_session_set_status(struct nc_session *session, NC_STATUS status)
162{
163 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200164 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200165 return;
166 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200167 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200168 return;
169 }
170
171 session->status = status;
172}
173
romanf578cd52023-10-19 09:47:40 +0200174API int
175nc_server_init_ctx(struct ly_ctx **ctx)
176{
177 int new_ctx = 0, i, ret = 0;
178 struct lys_module *module;
179 /* all features */
180 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
181 /* all features (module has no features) */
182 const char *ietf_netconf_monitoring_features[] = {NULL};
183
184 NC_CHECK_ARG_RET(NULL, ctx, 1);
185
186 if (!*ctx) {
187 /* context not given, create a new one */
188 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
189 ERR(NULL, "Couldn't create new libyang context.\n");
190 ret = 1;
191 goto cleanup;
192 }
193 new_ctx = 1;
194 }
195
196 if (new_ctx) {
197 /* new context created, implement both modules */
198 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
199 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
200 ret = 1;
201 goto cleanup;
202 }
203
204 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
205 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
206 ret = 1;
207 goto cleanup;
208 }
209
210 goto cleanup;
211 }
212
213 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
214 if (module) {
215 /* ietf-netconf module is present, check features */
216 for (i = 0; ietf_netconf_features[i]; i++) {
217 if (lys_feature_value(module, ietf_netconf_features[i])) {
218 /* feature not found, enable all of them */
219 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
220 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
221 ret = 1;
222 goto cleanup;
223 }
224
225 break;
226 }
227 }
228 } else {
229 /* ietf-netconf module not found, add it */
230 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
231 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
232 ret = 1;
233 goto cleanup;
234 }
235 }
236
237 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
238 if (!module) {
239 /* ietf-netconf-monitoring module not found, add it */
240 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
241 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
242 ret = 1;
243 goto cleanup;
244 }
245 }
246
247cleanup:
248 if (new_ctx && ret) {
249 ly_ctx_destroy(*ctx);
250 *ctx = NULL;
251 }
252 return ret;
253}
254
roman96c27f92023-11-02 11:09:46 +0100255#ifdef NC_ENABLED_SSH_TLS
256
roman450c00b2023-11-02 10:31:45 +0100257API void
258nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
259 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
260 void *new_session_cb_data)
261{
262 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
263
264 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
265 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
266 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
267 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
268 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
269}
270
roman96c27f92023-11-02 11:09:46 +0100271#endif
272
Michal Vasko086311b2016-01-08 09:53:11 +0100273int
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +0200274nc_sock_listen_inet(const char *address, uint16_t port)
Michal Vasko086311b2016-01-08 09:53:11 +0100275{
Michal Vasko06c860d2018-07-09 16:08:52 +0200276 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100277 int is_ipv4, sock;
278 struct sockaddr_storage saddr;
279
280 struct sockaddr_in *saddr4;
281 struct sockaddr_in6 *saddr6;
282
Michal Vasko086311b2016-01-08 09:53:11 +0100283 if (!strchr(address, ':')) {
284 is_ipv4 = 1;
285 } else {
286 is_ipv4 = 0;
287 }
288
289 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
290 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200291 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100292 goto fail;
293 }
294
Michal Vaskobe52dc22018-10-17 09:28:17 +0200295 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200296 opt = 1;
297 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200298 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200299 goto fail;
300 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100301 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200302 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100303 goto fail;
304 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200305
Michal Vaskof22d5ff2020-04-15 11:10:27 +0200306 memset(&saddr, 0, sizeof(struct sockaddr_storage));
Michal Vasko086311b2016-01-08 09:53:11 +0100307 if (is_ipv4) {
308 saddr4 = (struct sockaddr_in *)&saddr;
309
310 saddr4->sin_family = AF_INET;
311 saddr4->sin_port = htons(port);
312
313 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200314 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100315 goto fail;
316 }
317
318 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200319 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100320 goto fail;
321 }
322
323 } else {
324 saddr6 = (struct sockaddr_in6 *)&saddr;
325
326 saddr6->sin6_family = AF_INET6;
327 saddr6->sin6_port = htons(port);
328
329 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200330 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100331 goto fail;
332 }
333
334 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200335 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100336 goto fail;
337 }
338 }
339
Michal Vaskofb89d772016-01-08 12:25:35 +0100340 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200341 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100342 goto fail;
343 }
Michal Vasko086311b2016-01-08 09:53:11 +0100344 return sock;
345
346fail:
347 if (sock > -1) {
348 close(sock);
349 }
350
351 return -1;
352}
353
Michal Vaskoc429a8e2024-01-15 15:04:57 +0100354/**
355 * @brief Create a listening socket (AF_UNIX).
356 *
357 * @param[in] opts The server options (unix permissions and address of the socket).
358 * @return Listening socket, -1 on error.
359 */
360static int
roman83683fb2023-02-24 09:15:23 +0100361nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200362{
363 struct sockaddr_un sun;
364 int sock = -1;
365
roman83683fb2023-02-24 09:15:23 +0100366 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
367 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 +0200368 goto fail;
369 }
370
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200371 sock = socket(AF_UNIX, SOCK_STREAM, 0);
372 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200373 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200374 goto fail;
375 }
376
377 memset(&sun, 0, sizeof(sun));
378 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100379 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200380
381 unlink(sun.sun_path);
382 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100383 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200384 goto fail;
385 }
386
387 if (opts->mode != (mode_t)-1) {
388 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200389 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200390 goto fail;
391 }
392 }
393
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200394 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200395 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200396 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200397 goto fail;
398 }
399 }
400
401 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100402 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200403 goto fail;
404 }
405
406 return sock;
407
408fail:
409 if (sock > -1) {
410 close(sock);
411 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200412 return -1;
413}
414
aPiecek90ff0242021-02-14 14:58:01 +0100415/**
416 * @brief Evaluate socket name for AF_UNIX socket.
417 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
418 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
419 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
420 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
421 * @return -1 in case of error. Parameter host is set to NULL.
422 */
423static int
424sock_host_unix(int acc_sock_fd, char **host)
425{
426 char *sun_path;
427 struct sockaddr_storage saddr;
428 socklen_t addr_len;
429
430 *host = NULL;
431 saddr.ss_family = AF_UNIX;
432 addr_len = sizeof(saddr);
433
434 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200435 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100436 return -1;
437 }
438
439 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
440 if (!sun_path) {
441 /* stream socket is unnamed */
442 return 0;
443 }
444
roman3a95bb22023-10-26 11:07:17 +0200445 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100446
447 return 0;
448}
449
450/**
451 * @brief Evaluate socket name and port number for AF_INET socket.
452 * @param[in] addr is pointing to structure filled by accept function which was successful.
453 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
454 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
455 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
456 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
457 */
458static int
459sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
460{
461 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200462 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100463
aPiecek3da9b342021-02-18 15:00:03 +0100464 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100465 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100466 free(*host);
467 *host = NULL;
468 return -1;
469 }
470
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200471 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100472
473 return 0;
474}
475
476/**
477 * @brief Evaluate socket name and port number for AF_INET6 socket.
478 * @param[in] addr is pointing to structure filled by accept function which was successful.
479 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
480 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
481 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
482 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
483 */
484static int
485sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
486{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200487 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200488 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100489
aPiecek3da9b342021-02-18 15:00:03 +0100490 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100491 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100492 free(*host);
493 *host = NULL;
494 return -1;
495 }
496
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200497 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100498
499 return 0;
500}
501
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200502int
Michal Vasko6f865982023-11-21 12:10:42 +0100503nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
504 uint16_t *port, uint16_t *idx)
Michal Vasko086311b2016-01-08 09:53:11 +0100505{
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200506 uint16_t i, j, pfd_count, client_port;
507 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100508 struct pollfd *pfd;
509 struct sockaddr_storage saddr;
510 socklen_t saddr_len = sizeof(saddr);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200511 int ret, client_sock, sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100512
513 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200514 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100515
romanf578cd52023-10-19 09:47:40 +0200516 /* LOCK */
517 pthread_mutex_lock(bind_lock);
518
Michal Vaskoac2f6182017-01-30 14:32:03 +0100519 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200520 if (binds[i].sock < 0) {
521 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200522 continue;
523 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200524 if (binds[i].pollin) {
525 binds[i].pollin = 0;
526 /* leftover pollin */
527 sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100528 break;
529 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100530 pfd[pfd_count].fd = binds[i].sock;
531 pfd[pfd_count].events = POLLIN;
532 pfd[pfd_count].revents = 0;
533
534 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100535 }
536
Michal Vasko0a3f3752016-10-13 14:58:38 +0200537 if (sock == -1) {
538 /* poll for a new connection */
Michal Vasko63b92d62024-04-29 10:04:56 +0200539 ret = nc_poll(pfd, pfd_count, timeout);
540 if (ret < 1) {
541 free(pfd);
Michal Vaskof54cd352017-02-22 13:42:02 +0100542
romanf578cd52023-10-19 09:47:40 +0200543 /* UNLOCK */
544 pthread_mutex_unlock(bind_lock);
Michal Vasko63b92d62024-04-29 10:04:56 +0200545
546 return ret;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200547 }
Michal Vasko086311b2016-01-08 09:53:11 +0100548
Michal Vaskoac2f6182017-01-30 14:32:03 +0100549 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
550 /* adjust i so that indices in binds and pfd always match */
551 while (binds[i].sock != pfd[j].fd) {
552 ++i;
553 }
554
555 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200556 --ret;
557
558 if (!ret) {
559 /* the last socket with an event, use it */
Michal Vaskoac2f6182017-01-30 14:32:03 +0100560 sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200561 break;
562 } else {
563 /* just remember the event for next time */
564 binds[i].pollin = 1;
565 }
566 }
Michal Vasko086311b2016-01-08 09:53:11 +0100567 }
568 }
569 free(pfd);
Michal Vasko086311b2016-01-08 09:53:11 +0100570 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100571 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200572 /* UNLOCK */
573 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100574 return -1;
575 }
576
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200577 /* accept connection */
578 client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
579 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200580 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200581 /* UNLOCK */
582 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100583 return -1;
584 }
585
Michal Vasko0190bc32016-03-02 15:47:49 +0100586 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200587 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200588 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200589 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100590 }
591
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200592 /* learn information about the client end */
593 if (saddr.ss_family == AF_UNIX) {
594 if (sock_host_unix(client_sock, &client_address)) {
595 goto fail;
596 }
597 client_port = 0;
598 } else if (saddr.ss_family == AF_INET) {
599 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
600 goto fail;
601 }
602 } else if (saddr.ss_family == AF_INET6) {
603 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
604 goto fail;
605 }
606 } else {
607 ERR(NULL, "Source host of an unknown protocol family.");
608 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100609 }
Michal Vasko086311b2016-01-08 09:53:11 +0100610
aPiecek90ff0242021-02-14 14:58:01 +0100611 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200612 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100613 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200614 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 +0100615 }
616
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200617 if (host) {
618 *host = client_address;
619 } else {
620 free(client_address);
621 }
622 if (port) {
623 *port = client_port;
624 }
625 if (idx) {
626 *idx = i;
627 }
romanf578cd52023-10-19 09:47:40 +0200628 /* UNLOCK */
629 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200630 return client_sock;
631
632fail:
633 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200634 /* UNLOCK */
635 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200636 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100637}
638
Michal Vasko238b6c12021-12-14 15:14:09 +0100639API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200640nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100641{
Michal Vasko77367452021-02-16 16:32:18 +0100642 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100643 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100644 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100645 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100646 const struct lysp_submodule *submodule = NULL;
647 struct lyd_node *child, *err, *data = NULL;
648 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100649
Michal Vasko77367452021-02-16 16:32:18 +0100650 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100651 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200652 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100653 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200654 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200655 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100656 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500657 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100658 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200659 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100660 }
661 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200662 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100663
Michal Vasko77367452021-02-16 16:32:18 +0100664 /* check revision */
665 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100666 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100667 nc_err_set_msg(err, "The requested version is not supported.", "en");
668 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100669 }
670
Michal Vasko77367452021-02-16 16:32:18 +0100671 if (revision) {
672 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100673 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100674 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100675 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100676 }
677 } else {
678 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100679 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100680 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100681 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100682 }
683 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100684 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100685 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200686 }
Michal Vasko77367452021-02-16 16:32:18 +0100687 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100688 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200689 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100690 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100691 }
692
693 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100694 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100695 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100696 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100697 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100698 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100699 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100700 nc_err_set_msg(err, "The requested format is not supported.", "en");
701 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100702 }
Michal Vasko77367452021-02-16 16:32:18 +0100703
704 /* print */
705 ly_out_new_memory(&model_data, 0, &out);
706 if (module) {
707 lys_print_module(out, module, outformat, 0, 0);
708 } else {
709 lys_print_submodule(out, submodule, outformat, 0, 0);
710 }
711 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200712 if (!model_data) {
713 ERRINT;
714 return NULL;
715 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100716
Michal Vasko9b1a9522021-03-15 16:24:26 +0100717 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100718 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100719 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100720 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200721 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100722 return NULL;
723 }
Michal Vasko58791da2024-02-26 13:52:59 +0100724 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 +0100725 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100726 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100727 lyd_free_tree(data);
728 return NULL;
729 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100730
Radek Krejci36dfdb32016-09-01 16:56:35 +0200731 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100732}
733
Michal Vasko238b6c12021-12-14 15:14:09 +0100734API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100735nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100736{
Michal Vasko428087d2016-01-14 16:04:28 +0100737 session->term_reason = NC_SESSION_TERM_CLOSED;
738 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100739}
740
Michal Vasko93224072021-11-09 12:14:28 +0100741/**
742 * @brief Initialize a context with default RPC callbacks if none are set.
743 *
744 * @param[in] ctx Context to initialize.
745 */
746static void
romanf578cd52023-10-19 09:47:40 +0200747nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100748{
Michal Vasko77367452021-02-16 16:32:18 +0100749 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100750
Michal Vasko238b6c12021-12-14 15:14:09 +0100751 if (global_rpc_clb) {
752 /* expect it to handle these RPCs as well */
753 return;
754 }
755
Michal Vasko05ba9df2016-01-13 14:40:27 +0100756 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100757 rpc = NULL;
758 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
759 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
760 }
Michal Vasko88639e92017-08-03 14:38:10 +0200761 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100762 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100763 }
764
Michal Vasko93224072021-11-09 12:14:28 +0100765 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100766 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200767 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100768 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100769 }
Michal Vasko93224072021-11-09 12:14:28 +0100770}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100771
Michal Vasko93224072021-11-09 12:14:28 +0100772API int
773nc_server_init(void)
774{
Michal Vasko29f2f022024-03-13 09:06:48 +0100775 pthread_rwlockattr_t *attr_p = NULL;
Michal Vasko93224072021-11-09 12:14:28 +0100776 int r;
777
romand82caf12024-03-05 14:21:39 +0100778 ATOMIC_STORE_RELAXED(server_opts.new_session_id, 1);
779 ATOMIC_STORE_RELAXED(server_opts.new_client_id, 1);
Michal Vaskob48aa812016-01-18 14:13:09 +0100780
Michal Vasko93224072021-11-09 12:14:28 +0100781#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
Michal Vasko29f2f022024-03-13 09:06:48 +0100782 pthread_rwlockattr_t attr;
783
Michal Vasko93224072021-11-09 12:14:28 +0100784 if ((r = pthread_rwlockattr_init(&attr))) {
785 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
786 goto error;
787 }
788 attr_p = &attr;
789 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
790 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
791 goto error;
792 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700793#endif
Michal Vasko93224072021-11-09 12:14:28 +0100794
romanf578cd52023-10-19 09:47:40 +0200795 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100796 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
797 goto error;
798 }
799 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
800 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
801 goto error;
802 }
803
804 if (attr_p) {
805 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000806 }
romanf578cd52023-10-19 09:47:40 +0200807
808#ifdef NC_ENABLED_SSH_TLS
809 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
810 ERR(NULL, "%s: failed to init CURL.", __func__);
811 goto error;
812 }
Michal Vasko5788c802024-05-03 16:14:40 +0200813
814 /* optional for dynamic library, mandatory for static */
815 if (ssh_init()) {
816 ERR(NULL, "%s: failed to init libssh.", __func__);
817 goto error;
818 }
romanf578cd52023-10-19 09:47:40 +0200819#endif
820
821 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
822 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
823 goto error;
824 }
825
Michal Vasko086311b2016-01-08 09:53:11 +0100826 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100827
828error:
829 if (attr_p) {
830 pthread_rwlockattr_destroy(attr_p);
831 }
832 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100833}
834
Michal Vaskob48aa812016-01-18 14:13:09 +0100835API void
836nc_server_destroy(void)
837{
romana2ff4ef2024-01-19 14:41:46 +0100838 uint32_t i, endpt_count;
Radek Krejci658782b2016-12-04 22:04:55 +0100839
840 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100841 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100842 }
843 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200844 server_opts.capabilities = NULL;
845 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200846 if (server_opts.content_id_data && server_opts.content_id_data_free) {
847 server_opts.content_id_data_free(server_opts.content_id_data);
848 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200849
romanf578cd52023-10-19 09:47:40 +0200850 nc_server_config_listen(NULL, NC_OP_DELETE);
851 nc_server_config_ch(NULL, NC_OP_DELETE);
852
romana2ff4ef2024-01-19 14:41:46 +0100853 endpt_count = server_opts.endpt_count;
854 for (i = 0; i < endpt_count; i++) {
855 if (server_opts.endpts[i].ti == NC_TI_UNIX) {
856 _nc_server_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]);
857 }
858 }
859
romanf578cd52023-10-19 09:47:40 +0200860 pthread_mutex_destroy(&server_opts.bind_lock);
861
862#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100863 free(server_opts.authkey_path_fmt);
864 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100865 free(server_opts.pam_config_name);
866 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200867 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
868 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
869 }
870 server_opts.interactive_auth_data = NULL;
871 server_opts.interactive_auth_data_free = NULL;
872
romanf578cd52023-10-19 09:47:40 +0200873 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
874 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
875 curl_global_cleanup();
Michal Vasko5788c802024-05-03 16:14:40 +0200876 ssh_finalize();
romanf578cd52023-10-19 09:47:40 +0200877#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100878}
879
Michal Vasko086311b2016-01-08 09:53:11 +0100880API int
881nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
882{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200883 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200884 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200885 return -1;
886 } 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 +0200887 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100888 return -1;
889 }
890
romanf578cd52023-10-19 09:47:40 +0200891 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
892 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100893 return 0;
894}
895
Michal Vasko1a38c862016-01-15 15:50:07 +0100896API void
Michal Vasko55f03972016-04-13 08:56:01 +0200897nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
898{
899 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200900 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200901 return;
902 }
903
904 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200905 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200906 }
907 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200908 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200909 }
910}
911
Michal Vasko55f03972016-04-13 08:56:01 +0200912API int
Radek Krejci658782b2016-12-04 22:04:55 +0100913nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200914{
Michal Vasko93224072021-11-09 12:14:28 +0100915 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100916
917 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200918 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100919 return EXIT_FAILURE;
920 }
921
Michal Vasko93224072021-11-09 12:14:28 +0100922 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200923 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100924 server_opts.capabilities = mem;
925
926 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
927 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100928
929 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200930}
931
Michal Vasko1a38c862016-01-15 15:50:07 +0100932API void
Michal Vasko1440a742021-03-31 11:11:03 +0200933nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
934 void (*free_user_data)(void *user_data))
935{
936 server_opts.content_id_clb = content_id_clb;
937 server_opts.content_id_data = user_data;
938 server_opts.content_id_data_free = free_user_data;
939}
940
Michal Vasko71090fc2016-05-24 16:37:28 +0200941API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100942nc_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 +0100943{
Michal Vasko71090fc2016-05-24 16:37:28 +0200944 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200945 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200946
romand82caf12024-03-05 14:21:39 +0100947 NC_CHECK_ARG_RET(NULL, ctx, username, fdin >= 0, fdout >= 0, session, NC_MSG_ERROR);
romanf578cd52023-10-19 09:47:40 +0200948
romand82caf12024-03-05 14:21:39 +0100949 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
Michal Vasko086311b2016-01-08 09:53:11 +0100950
Michal Vasko93224072021-11-09 12:14:28 +0100951 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200952 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100953
Michal Vasko086311b2016-01-08 09:53:11 +0100954 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200955 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200956 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100957 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100958
Michal Vasko086311b2016-01-08 09:53:11 +0100959 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100960 (*session)->ti_type = NC_TI_FD;
961 (*session)->ti.fd.in = fdin;
962 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100963
Michal Vasko93224072021-11-09 12:14:28 +0100964 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100965 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100966 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100967
Michal Vaskob48aa812016-01-18 14:13:09 +0100968 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200969 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100970
Michal Vasko086311b2016-01-08 09:53:11 +0100971 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200972 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200973 if (msgtype != NC_MSG_HELLO) {
974 nc_session_free(*session, NULL);
975 *session = NULL;
976 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100977 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200978
Michal Vaskod8a74192023-02-06 15:51:50 +0100979 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200980 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100981 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100982 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200983
Michal Vasko1a38c862016-01-15 15:50:07 +0100984 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100985
Michal Vasko71090fc2016-05-24 16:37:28 +0200986 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100987}
Michal Vasko9e036d52016-01-08 10:49:26 +0100988
Michal Vaskob30b99c2016-07-26 11:35:43 +0200989static void
Michal Vasko74c345f2018-02-07 10:37:11 +0100990nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
991{
992 uint8_t q_last;
993
994 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
995 ERRINT;
996 return;
997 }
998
999 /* get a unique queue value (by adding 1 to the last added value, if any) */
1000 if (ps->queue_len) {
1001 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1002 *id = ps->queue[q_last] + 1;
1003 } else {
1004 *id = 0;
1005 }
1006
1007 /* add the id into the queue */
1008 ++ps->queue_len;
1009 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1010 ps->queue[q_last] = *id;
1011}
1012
1013static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001014nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1015{
Michal Vasko74c345f2018-02-07 10:37:11 +01001016 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001017
1018 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001019 /* get the actual queue idx */
1020 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001021
1022 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001023 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001024 /* another equal value, simply cannot be */
1025 ERRINT;
1026 }
Michal Vaskod8340032018-02-12 14:41:00 +01001027 if (found == 2) {
1028 /* move the following values */
1029 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1030 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001031 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001032 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001033 if (i == 0) {
1034 found = 1;
1035 } else {
1036 /* this is not okay, our id is in the middle of the queue */
1037 found = 2;
1038 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001039 }
1040 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001041 if (!found) {
1042 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001043 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001044 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001045
Michal Vasko103fe632018-02-12 16:37:45 +01001046 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001047 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001048 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001049 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1050 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001051}
1052
Michal Vaskof04a52a2016-04-07 10:52:10 +02001053int
Michal Vasko26043172016-07-26 14:08:59 +02001054nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001055{
1056 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001057 struct timespec ts;
1058
Michal Vaskobe86fe32016-04-07 10:43:03 +02001059 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001060 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001061 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001062 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001063 return -1;
1064 }
1065
Michal Vasko74c345f2018-02-07 10:37:11 +01001066 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001067 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001068 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001069 pthread_mutex_unlock(&ps->lock);
1070 return -1;
1071 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001072
1073 /* add ourselves into the queue */
1074 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001075 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 +02001076 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001077
1078 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001079 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001080 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001081
Michal Vaskod8a74192023-02-06 15:51:50 +01001082 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001083 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301084 /**
1085 * This may happen when another thread releases the lock and broadcasts the condition
1086 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1087 * but when actually this thread was ready for condition.
1088 */
preetbhansali629dfc42018-12-17 16:04:40 +05301089 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301090 break;
1091 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001092
Michal Vasko05532772021-06-03 12:12:38 +02001093 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001094 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001095 nc_ps_queue_remove_id(ps, *id);
1096 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001097 return -1;
1098 }
1099 }
1100
Michal Vaskobe86fe32016-04-07 10:43:03 +02001101 /* UNLOCK */
1102 pthread_mutex_unlock(&ps->lock);
1103
1104 return 0;
1105}
1106
Michal Vaskof04a52a2016-04-07 10:52:10 +02001107int
Michal Vasko26043172016-07-26 14:08:59 +02001108nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001109{
1110 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001111
1112 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001113 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001114 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001115 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001116 ret = -1;
1117 }
1118
Michal Vaskob30b99c2016-07-26 11:35:43 +02001119 /* we must be the first, it was our turn after all, right? */
1120 if (ps->queue[ps->queue_begin] != id) {
1121 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001122 /* UNLOCK */
1123 if (!ret) {
1124 pthread_mutex_unlock(&ps->lock);
1125 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001126 return -1;
1127 }
1128
Michal Vaskobe86fe32016-04-07 10:43:03 +02001129 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001130 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001131 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 +02001132 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001133
1134 /* broadcast to all other threads that the queue moved */
1135 pthread_cond_broadcast(&ps->cond);
1136
Michal Vaskobe86fe32016-04-07 10:43:03 +02001137 /* UNLOCK */
1138 if (!ret) {
1139 pthread_mutex_unlock(&ps->lock);
1140 }
1141
1142 return ret;
1143}
1144
Michal Vasko428087d2016-01-14 16:04:28 +01001145API struct nc_pollsession *
1146nc_ps_new(void)
1147{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001148 struct nc_pollsession *ps;
1149
1150 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001151 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001152 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001153 pthread_mutex_init(&ps->lock, NULL);
1154
1155 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001156}
1157
1158API void
1159nc_ps_free(struct nc_pollsession *ps)
1160{
fanchanghu3d4e7212017-08-09 09:42:30 +08001161 uint16_t i;
1162
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001163 if (!ps) {
1164 return;
1165 }
1166
Michal Vaskobe86fe32016-04-07 10:43:03 +02001167 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001168 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001169 }
1170
fanchanghu3d4e7212017-08-09 09:42:30 +08001171 for (i = 0; i < ps->session_count; i++) {
1172 free(ps->sessions[i]);
1173 }
1174
Michal Vasko428087d2016-01-14 16:04:28 +01001175 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001176 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001177 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001178
Michal Vasko428087d2016-01-14 16:04:28 +01001179 free(ps);
1180}
1181
1182API int
1183nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1184{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001185 uint8_t q_id;
1186
romanf578cd52023-10-19 09:47:40 +02001187 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001188
Michal Vasko48a63ed2016-03-01 09:48:21 +01001189 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001190 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001191 return -1;
1192 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001193
Michal Vasko428087d2016-01-14 16:04:28 +01001194 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001195 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001196 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001197 ERRMEM;
1198 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001199 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001200 return -1;
1201 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001202 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1203 if (!ps->sessions[ps->session_count - 1]) {
1204 ERRMEM;
1205 --ps->session_count;
1206 /* UNLOCK */
1207 nc_ps_unlock(ps, q_id, __func__);
1208 return -1;
1209 }
1210 ps->sessions[ps->session_count - 1]->session = session;
1211 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001212
Michal Vasko48a63ed2016-03-01 09:48:21 +01001213 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001214 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001215}
1216
Michal Vasko48a63ed2016-03-01 09:48:21 +01001217static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001218_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001219{
1220 uint16_t i;
1221
Radek Krejcid5f978f2016-03-03 13:14:45 +01001222 if (index >= 0) {
1223 i = (uint16_t)index;
1224 goto remove;
1225 }
Michal Vasko428087d2016-01-14 16:04:28 +01001226 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001227 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001228remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001229 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001230 if (i <= ps->session_count) {
1231 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001232 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001233 }
1234 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001235 free(ps->sessions);
1236 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001237 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001238 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001239 return 0;
1240 }
1241 }
1242
Michal Vaskof0537d82016-01-29 14:42:38 +01001243 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001244}
1245
Michal Vasko48a63ed2016-03-01 09:48:21 +01001246API int
1247nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1248{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001249 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001250 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001251
romanf578cd52023-10-19 09:47:40 +02001252 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001253
1254 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001255 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001256 return -1;
1257 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001258
Radek Krejcid5f978f2016-03-03 13:14:45 +01001259 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001260
1261 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001262 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001263
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001264 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001265}
1266
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001267API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001268nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001269{
1270 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001271 struct nc_session *ret = NULL;
1272
romanf578cd52023-10-19 09:47:40 +02001273 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001274
1275 /* LOCK */
1276 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1277 return NULL;
1278 }
1279
Michal Vasko4871c9d2017-10-09 14:48:39 +02001280 if (idx < ps->session_count) {
1281 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001282 }
1283
1284 /* UNLOCK */
1285 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1286
1287 return ret;
1288}
1289
Michal Vasko3ec3b112022-07-21 12:32:33 +02001290API struct nc_session *
1291nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1292{
1293 uint8_t q_id;
1294 uint16_t i;
1295 struct nc_session *ret = NULL;
1296
romanf578cd52023-10-19 09:47:40 +02001297 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001298
1299 /* LOCK */
1300 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1301 return NULL;
1302 }
1303
1304 for (i = 0; i < ps->session_count; ++i) {
1305 if (match_cb(ps->sessions[i]->session, cb_data)) {
1306 ret = ps->sessions[i]->session;
1307 break;
1308 }
1309 }
1310
1311 /* UNLOCK */
1312 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1313
1314 return ret;
1315}
1316
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001317API uint16_t
1318nc_ps_session_count(struct nc_pollsession *ps)
1319{
Michal Vasko47003942019-03-14 12:25:23 +01001320 uint8_t q_id;
1321 uint16_t session_count;
1322
romanf578cd52023-10-19 09:47:40 +02001323 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001324
Michal Vasko47003942019-03-14 12:25:23 +01001325 /* LOCK (just for memory barrier so that we read the current value) */
1326 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1327 return 0;
1328 }
1329
1330 session_count = ps->session_count;
1331
1332 /* UNLOCK */
1333 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1334
1335 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001336}
1337
Michal Vasko77e83572022-07-21 15:31:15 +02001338static NC_MSG_TYPE
1339recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1340{
1341 struct lyd_attr *attr;
1342
1343 assert(envp && !envp->schema);
1344
1345 /* find the message-id attribute */
1346 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1347 if (!strcmp(attr->name.name, "message-id")) {
1348 break;
1349 }
1350 }
1351
1352 if (!attr) {
1353 ERR(session, "Received an <rpc> without a message-id.");
1354 return NC_MSG_REPLY_ERR_MSGID;
1355 }
1356
1357 return NC_MSG_RPC;
1358}
1359
Michal Vasko131120a2018-05-29 15:44:02 +02001360/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001361 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001362 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001363 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001364 * NC_PSPOLL_RPC
1365 */
1366static int
Michal Vasko131120a2018-05-29 15:44:02 +02001367nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001368{
Michal Vasko77367452021-02-16 16:32:18 +01001369 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001370 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001371 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001372 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001373
romanf578cd52023-10-19 09:47:40 +02001374 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1375
1376 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001377 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001378 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001379 }
1380
Michal Vasko93224072021-11-09 12:14:28 +01001381 *rpc = NULL;
1382
Michal Vasko77367452021-02-16 16:32:18 +01001383 /* get a message */
1384 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1385 if (r == -2) {
1386 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001387 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001388 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001389 }
1390 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001391 return NC_PSPOLL_ERROR;
1392 } else if (!r) {
1393 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001394 }
1395
Michal Vasko77367452021-02-16 16:32:18 +01001396 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001397 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001398
1399 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001400 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1401 /* check message-id */
1402 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1403 /* valid RPC */
1404 ret = NC_PSPOLL_RPC;
1405 } else {
1406 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001407 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1408 }
1409 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001410 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001411 if ((*rpc)->envp) {
1412 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001413 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
Michal Vasko58791da2024-02-26 13:52:59 +01001414 nc_err_set_msg(e, ly_err_last(session->ctx)->msg, "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001415 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001416 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001417 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1418 * the server (RFC 6241 sec. 3) */
1419 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001420 } else {
1421 /* at least set the return value */
1422 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001423 }
Michal Vasko77367452021-02-16 16:32:18 +01001424 }
1425
1426cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001427 if (reply) {
1428 /* send error reply */
1429 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1430 nc_server_reply_free(reply);
1431 if (r != NC_MSG_REPLY) {
1432 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1433 if (session->status != NC_STATUS_INVALID) {
1434 session->status = NC_STATUS_INVALID;
1435 session->term_reason = NC_SESSION_TERM_OTHER;
1436 }
1437 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001438
1439 /* bad RPC and an error reply sent */
1440 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001441 }
1442
Michal Vasko77367452021-02-16 16:32:18 +01001443 ly_in_free(msg, 1);
1444 if (ret != NC_PSPOLL_RPC) {
1445 nc_server_rpc_free(*rpc);
1446 *rpc = NULL;
1447 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001448 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001449}
1450
fanchanghu966f2de2016-07-21 02:28:57 -04001451API void
1452nc_set_global_rpc_clb(nc_rpc_clb clb)
1453{
1454 global_rpc_clb = clb;
1455}
1456
Radek Krejci93e80222016-10-03 13:34:25 +02001457API NC_MSG_TYPE
1458nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1459{
Michal Vasko131120a2018-05-29 15:44:02 +02001460 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001461
1462 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001463 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001464 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001465 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001466 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001467 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001468 return NC_MSG_ERROR;
1469 }
1470
Michal Vasko131120a2018-05-29 15:44:02 +02001471 /* we do not need RPC lock for this, IO lock will be acquired properly */
1472 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001473 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001474 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001475 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001476
Michal Vasko131120a2018-05-29 15:44:02 +02001477 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001478}
1479
Michal Vaskof9467762023-03-28 09:02:08 +02001480/**
1481 * @brief Send a reply acquiring IO lock as needed.
1482 * Session RPC lock must be held!
1483 *
1484 * @param[in] session Session to use.
1485 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1486 * @param[in] rpc RPC to sent.
1487 * @return 0 on success.
1488 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1489 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001490 */
1491static int
Michal Vasko93224072021-11-09 12:14:28 +01001492nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001493{
1494 nc_rpc_clb clb;
1495 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001496 const struct lysc_node *rpc_act = NULL;
1497 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001498 int ret = 0;
1499 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001500
Michal Vasko4a827e52016-03-03 10:59:00 +01001501 if (!rpc) {
1502 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001503 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001504 }
1505
Michal Vasko77367452021-02-16 16:32:18 +01001506 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001507 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001508 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001509 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001510 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001511 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001512 if (elem->schema->nodetype == LYS_ACTION) {
1513 rpc_act = elem->schema;
1514 break;
1515 }
Michal Vasko77367452021-02-16 16:32:18 +01001516 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001517 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001518 if (!rpc_act) {
1519 ERRINT;
1520 return NC_PSPOLL_ERROR;
1521 }
1522 }
1523
1524 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001525 if (!global_rpc_clb) {
1526 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001527 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 +03001528 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001529 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001530 }
Michal Vasko428087d2016-01-14 16:04:28 +01001531 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001532 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001533 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001534 }
1535
1536 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001537 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001538 }
Michal Vasko77367452021-02-16 16:32:18 +01001539 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001540 if (reply->type == NC_RPL_ERROR) {
1541 ret |= NC_PSPOLL_REPLY_ERROR;
1542 }
1543 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001544
Michal Vasko131120a2018-05-29 15:44:02 +02001545 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001546 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001547 ret |= NC_PSPOLL_ERROR;
1548 }
Michal Vasko428087d2016-01-14 16:04:28 +01001549
1550 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1551 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1552 session->status = NC_STATUS_INVALID;
1553 }
1554
Michal Vasko71090fc2016-05-24 16:37:28 +02001555 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001556}
1557
Michal Vaskof9467762023-03-28 09:02:08 +02001558/**
1559 * @brief Poll a session from pspoll acquiring IO lock as needed.
1560 * Session must be running and session RPC lock held!
1561 *
1562 * @param[in] session Session to use.
1563 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1564 * @param[in] now_mono Current monotonic timestamp.
1565 * @param[in,out] msg Message to fill in case of an error.
1566 * @return NC_PSPOLL_RPC if some application data are available.
1567 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1568 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1569 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1570 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1571 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001572 */
1573static int
Michal Vasko131120a2018-05-29 15:44:02 +02001574nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001575{
Michal Vasko9a327362017-01-11 11:31:46 +01001576 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001577 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001578
romanf578cd52023-10-19 09:47:40 +02001579#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001580 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001581 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001582#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001583
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001584 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001585 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001586 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001587 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001588 session->status = NC_STATUS_INVALID;
1589 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1590 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1591 }
1592
Michal Vasko131120a2018-05-29 15:44:02 +02001593 r = nc_session_io_lock(session, io_timeout, __func__);
1594 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001595 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001596 return NC_PSPOLL_ERROR;
1597 } else if (!r) {
1598 return NC_PSPOLL_TIMEOUT;
1599 }
1600
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001601 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001602#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001603 case NC_TI_LIBSSH:
romanf578cd52023-10-19 09:47:40 +02001604 ssh_msg = ssh_message_get(session->ti.libssh.session);
1605 if (ssh_msg) {
1606 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1607 if (session->ti.libssh.next) {
1608 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1609 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1610 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1611 /* new NETCONF SSH channel */
1612 ret = NC_PSPOLL_SSH_CHANNEL;
1613 break;
1614 }
1615 }
1616 if (new != session) {
1617 ssh_message_free(ssh_msg);
1618 break;
1619 }
1620 }
1621 if (!ret) {
1622 /* just some SSH message */
1623 ret = NC_PSPOLL_SSH_MSG;
1624 }
1625 ssh_message_free(ssh_msg);
1626
1627 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1628 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1629 */
1630 break;
1631 }
1632
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001633 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001634 if (r == SSH_EOF) {
1635 sprintf(msg, "SSH channel unexpected EOF");
1636 session->status = NC_STATUS_INVALID;
1637 session->term_reason = NC_SESSION_TERM_DROPPED;
1638 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1639 } else if (r == SSH_ERROR) {
1640 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001641 session->status = NC_STATUS_INVALID;
1642 session->term_reason = NC_SESSION_TERM_OTHER;
1643 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001644 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001645 /* no application data received */
1646 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001647 } else {
1648 /* we have some application data */
1649 ret = NC_PSPOLL_RPC;
1650 }
1651 break;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001652 case NC_TI_OPENSSL:
1653 r = SSL_pending(session->ti.tls);
1654 if (!r) {
1655 /* no data pending in the SSL buffer, poll fd */
1656 pfd.fd = SSL_get_rfd(session->ti.tls);
1657 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001658 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001659 ret = NC_PSPOLL_ERROR;
1660 break;
1661 }
1662 pfd.events = POLLIN;
1663 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001664 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001665
Michal Vasko63b92d62024-04-29 10:04:56 +02001666 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001667 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001668 session->status = NC_STATUS_INVALID;
1669 ret = NC_PSPOLL_ERROR;
1670 } else if (r > 0) {
1671 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001672 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001673 session->status = NC_STATUS_INVALID;
1674 session->term_reason = NC_SESSION_TERM_DROPPED;
1675 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1676 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001677 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001678 session->status = NC_STATUS_INVALID;
1679 session->term_reason = NC_SESSION_TERM_OTHER;
1680 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1681 } else {
1682 ret = NC_PSPOLL_RPC;
1683 }
1684 } else {
1685 ret = NC_PSPOLL_TIMEOUT;
1686 }
1687 } else {
1688 ret = NC_PSPOLL_RPC;
1689 }
1690 break;
romanf578cd52023-10-19 09:47:40 +02001691#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001692 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001693 case NC_TI_UNIX:
1694 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001695 pfd.events = POLLIN;
1696 pfd.revents = 0;
Michal Vasko63b92d62024-04-29 10:04:56 +02001697 r = nc_poll(&pfd, 1, 0);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001698
Michal Vasko63b92d62024-04-29 10:04:56 +02001699 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001700 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001701 session->status = NC_STATUS_INVALID;
1702 ret = NC_PSPOLL_ERROR;
1703 } else if (r > 0) {
1704 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001705 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001706 session->status = NC_STATUS_INVALID;
1707 session->term_reason = NC_SESSION_TERM_DROPPED;
1708 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1709 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001710 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001711 session->status = NC_STATUS_INVALID;
1712 session->term_reason = NC_SESSION_TERM_OTHER;
1713 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1714 } else {
1715 ret = NC_PSPOLL_RPC;
1716 }
1717 } else {
1718 ret = NC_PSPOLL_TIMEOUT;
1719 }
1720 break;
1721 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001722 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001723 ret = NC_PSPOLL_ERROR;
1724 break;
1725 }
1726
Michal Vasko131120a2018-05-29 15:44:02 +02001727 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001728 return ret;
1729}
1730
1731API int
1732nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1733{
Michal Vasko443faa02022-10-20 09:09:03 +02001734 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001735 uint8_t q_id;
1736 uint16_t i, j;
1737 char msg[256];
1738 struct timespec ts_timeout, ts_cur;
1739 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001740 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001741 struct nc_server_rpc *rpc = NULL;
1742
romanf578cd52023-10-19 09:47:40 +02001743 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001744
Michal Vaskoade892d2017-02-22 13:40:35 +01001745 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001746 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001747 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001748 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001749
Michal Vaskoade892d2017-02-22 13:40:35 +01001750 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001751 nc_ps_unlock(ps, q_id, __func__);
1752 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001753 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001754
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001755 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001756 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001757 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001758 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001759 }
1760
1761 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001762 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001763 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001764 if (ps->last_event_session == ps->session_count - 1) {
1765 i = j = 0;
1766 } else {
1767 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001768 }
Michal Vasko9a327362017-01-11 11:31:46 +01001769 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001770 cur_ps_session = ps->sessions[i];
1771 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001772
Michal Vasko131120a2018-05-29 15:44:02 +02001773 /* SESSION RPC LOCK */
1774 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001775 if (r == -1) {
1776 ret = NC_PSPOLL_ERROR;
1777 } else if (r == 1) {
1778 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001779 switch (cur_ps_session->state) {
1780 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001781 if (cur_session->status == NC_STATUS_RUNNING) {
1782 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001783 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001784
Michal Vasko131120a2018-05-29 15:44:02 +02001785 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001786 switch (ret) {
1787 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001788 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001789 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001790 break;
1791 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001792 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001793 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001794 break;
1795 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001796#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001797 case NC_PSPOLL_SSH_CHANNEL:
1798 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001799#endif /* NC_ENABLED_SSH_TLS */
fanchanghu3d4e7212017-08-09 09:42:30 +08001800 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001801 break;
1802 case NC_PSPOLL_RPC:
1803 /* let's keep the state busy, we are not done with this session */
1804 break;
1805 }
1806 } else {
1807 /* session is not fine, let the caller know */
1808 ret = NC_PSPOLL_SESSION_TERM;
1809 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1810 ret |= NC_PSPOLL_SESSION_ERROR;
1811 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001812 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001813 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001814 break;
1815 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001816 /* it definitely should not be busy because we have the lock */
1817 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001818 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001819 break;
1820 case NC_PS_STATE_INVALID:
1821 /* we got it locked, but it will be freed, let it be */
1822 ret = NC_PSPOLL_TIMEOUT;
1823 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001824 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001825
Michal Vasko131120a2018-05-29 15:44:02 +02001826 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001827 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001828 /* SESSION RPC UNLOCK */
1829 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001830 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001831 } else {
1832 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001833 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001834 }
Michal Vasko428087d2016-01-14 16:04:28 +01001835
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001836 /* something happened */
1837 if (ret != NC_PSPOLL_TIMEOUT) {
1838 break;
1839 }
1840
Michal Vasko9a327362017-01-11 11:31:46 +01001841 if (i == ps->session_count - 1) {
1842 i = 0;
1843 } else {
1844 ++i;
1845 }
1846 } while (i != j);
1847
Michal Vaskoade892d2017-02-22 13:40:35 +01001848 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001849 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001850 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001851
Michal Vaskod8a74192023-02-06 15:51:50 +01001852 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001853 /* final timeout */
1854 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001855 }
Michal Vasko428087d2016-01-14 16:04:28 +01001856 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001857 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001858
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001859 /* do we want to return the session? */
1860 switch (ret) {
1861 case NC_PSPOLL_RPC:
1862 case NC_PSPOLL_SESSION_TERM:
1863 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001864#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001865 case NC_PSPOLL_SSH_CHANNEL:
1866 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001867#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001868 if (session) {
1869 *session = cur_session;
1870 }
1871 ps->last_event_session = i;
1872 break;
1873 default:
1874 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001875 }
Michal Vasko428087d2016-01-14 16:04:28 +01001876
Michal Vaskoade892d2017-02-22 13:40:35 +01001877 /* PS UNLOCK */
1878 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001879
Michal Vasko131120a2018-05-29 15:44:02 +02001880 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001881 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001882 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001883 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1884 if (cur_session->status != NC_STATUS_RUNNING) {
1885 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001886 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001887 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001888 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001889 }
1890 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001891 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001892
Michal Vasko7f1ee932018-10-11 09:41:42 +02001893 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001894 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001895 if (cur_session->status != NC_STATUS_RUNNING) {
1896 ret |= NC_PSPOLL_SESSION_TERM;
1897 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1898 ret |= NC_PSPOLL_SESSION_ERROR;
1899 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001900 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001901 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001902 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001903 }
Michal Vasko428087d2016-01-14 16:04:28 +01001904 }
Michal Vasko77367452021-02-16 16:32:18 +01001905 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001906
Michal Vasko131120a2018-05-29 15:44:02 +02001907 /* SESSION RPC UNLOCK */
1908 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001909 }
1910
Michal Vasko48a63ed2016-03-01 09:48:21 +01001911 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001912}
1913
Michal Vaskod09eae62016-02-01 10:32:52 +01001914API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001915nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001916{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001917 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001918 uint16_t i;
1919 struct nc_session *session;
1920
Michal Vasko9a25e932016-02-01 10:36:42 +01001921 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001922 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001923 return;
1924 }
1925
Michal Vasko48a63ed2016-03-01 09:48:21 +01001926 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001927 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001928 return;
1929 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001930
Michal Vasko48a63ed2016-03-01 09:48:21 +01001931 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001932 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001933 nc_session_free(ps->sessions[i]->session, data_free);
1934 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001935 }
1936 free(ps->sessions);
1937 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001938 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001939 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001940 } else {
1941 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001942 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1943 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001944 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001945 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001946 continue;
1947 }
1948
1949 ++i;
1950 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001951 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001952
1953 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001954 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001955}
1956
romanfb3f7cf2023-11-30 16:10:09 +01001957int
1958nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1959{
1960 int sock = -1, set_addr, ret = 0;
1961
1962 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1963
1964 if (address) {
1965 set_addr = 1;
1966 } else {
1967 set_addr = 0;
1968 }
1969
1970 if (set_addr) {
1971 port = bind->port;
1972 } else {
1973 address = bind->address;
1974 }
1975
1976 /* we have all the information we need to create a listening socket */
1977 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1978 /* create new socket, close the old one */
1979 if (endpt->ti == NC_TI_UNIX) {
1980 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1981 } else {
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02001982 sock = nc_sock_listen_inet(address, port);
romanfb3f7cf2023-11-30 16:10:09 +01001983 }
1984
1985 if (sock == -1) {
1986 ret = 1;
1987 goto cleanup;
1988 }
1989
1990 if (bind->sock > -1) {
1991 close(bind->sock);
1992 }
1993 bind->sock = sock;
1994 }
1995
1996 if (sock > -1) {
1997 switch (endpt->ti) {
1998 case NC_TI_UNIX:
1999 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2000 break;
2001#ifdef NC_ENABLED_SSH_TLS
2002 case NC_TI_LIBSSH:
2003 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2004 break;
2005 case NC_TI_OPENSSL:
2006 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2007 break;
2008#endif /* NC_ENABLED_SSH_TLS */
2009 default:
2010 ERRINT;
2011 ret = 1;
2012 break;
2013 }
2014 }
2015
2016cleanup:
2017 return ret;
2018}
2019
Michal Vasko29f2f022024-03-13 09:06:48 +01002020#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
2021
Michal Vasko6f865982023-11-21 12:10:42 +01002022/**
2023 * @brief Get UID of the owner of a socket.
2024 *
2025 * @param[in] sock Socket to analyze.
2026 * @param[out] uid Socket owner UID.
2027 * @return 0 on success,
2028 * @return -1 on error.
2029 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002030static int
apropp-molex4e903c32020-04-20 03:06:58 -04002031nc_get_uid(int sock, uid_t *uid)
2032{
Michal Vasko6f865982023-11-21 12:10:42 +01002033 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002034
Michal Vaskod3910912020-04-20 09:12:49 +02002035#ifdef SO_PEERCRED
2036 struct ucred ucred;
2037 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002038
Michal Vaskod3910912020-04-20 09:12:49 +02002039 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002040 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2041 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002042 *uid = ucred.uid;
2043 }
2044#else
Michal Vasko6f865982023-11-21 12:10:42 +01002045 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002046#endif
2047
Michal Vasko6f865982023-11-21 12:10:42 +01002048 if (r < 0) {
2049 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002050 return -1;
2051 }
apropp-molex4e903c32020-04-20 03:06:58 -04002052 return 0;
2053}
2054
Michal Vasko29f2f022024-03-13 09:06:48 +01002055#endif
2056
apropp-molex4e903c32020-04-20 03:06:58 -04002057static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002058nc_accept_unix(struct nc_session *session, int sock)
2059{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002060#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002061 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002062 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002063
Michal Vasko5f352c52019-07-10 16:12:06 +02002064 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002065 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002066 char *buf = NULL;
2067 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002068
Michal Vaskod3910912020-04-20 09:12:49 +02002069 if (nc_get_uid(sock, &uid)) {
2070 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002071 return -1;
2072 }
2073
romanf6e32012023-04-24 15:51:26 +02002074 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002075 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002076 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002077 close(sock);
2078 return -1;
2079 }
2080
2081 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002082 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002083 if (username == NULL) {
2084 ERRMEM;
2085 close(sock);
2086 return -1;
2087 }
Michal Vasko93224072021-11-09 12:14:28 +01002088 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002089
2090 session->ti.unixsock.sock = sock;
2091
2092 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002093#else
Michal Vasko29f2f022024-03-13 09:06:48 +01002094 (void)session;
2095 (void)sock;
2096
Claus Klein22091912020-01-20 13:45:47 +01002097 return -1;
2098#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002099}
2100
Michal Vaskoe2713da2016-08-22 16:06:40 +02002101API int
romanfb3f7cf2023-11-30 16:10:09 +01002102nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2103{
2104 int ret = 0;
2105 void *tmp;
2106 uint16_t i;
2107
2108 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2109
2110 /* CONFIG LOCK */
2111 pthread_rwlock_wrlock(&server_opts.config_lock);
2112
2113 /* check name uniqueness */
2114 for (i = 0; i < server_opts.endpt_count; i++) {
2115 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2116 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2117 ret = 1;
2118 goto cleanup;
2119 }
2120 }
2121
2122 /* alloc a new endpoint */
2123 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2124 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2125 server_opts.endpts = tmp;
2126 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2127
2128 /* alloc a new bind */
2129 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2130 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2131 server_opts.binds = tmp;
2132 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2133 server_opts.binds[server_opts.endpt_count].sock = -1;
2134 server_opts.endpt_count++;
2135
2136 /* set name and ti */
2137 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2138 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2139 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2140
2141 /* set the bind data */
2142 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2143 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2144
2145 /* alloc unix opts */
2146 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2147 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2148
2149 /* set the opts data */
2150 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2151 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2152 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2153 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2154 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2155
2156 /* start listening */
2157 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2158 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2159 if (ret) {
2160 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2161 goto cleanup;
2162 }
2163
2164cleanup:
2165 /* CONFIG UNLOCK */
2166 pthread_rwlock_unlock(&server_opts.config_lock);
2167 return ret;
2168}
2169
2170static void
2171nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2172{
2173 if (bind->sock > -1) {
2174 close(bind->sock);
2175 }
2176
2177 unlink(bind->address);
2178 free(bind->address);
2179 free(opts->address);
2180
2181 free(opts);
2182}
2183
2184void
2185_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2186{
2187 free(endpt->name);
2188 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2189
2190 server_opts.endpt_count--;
2191 if (!server_opts.endpt_count) {
2192 free(server_opts.endpts);
2193 free(server_opts.binds);
2194 server_opts.endpts = NULL;
2195 server_opts.binds = NULL;
2196 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2197 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2198 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2199 }
2200}
2201
2202API void
2203nc_server_del_endpt_unix_socket(const char *endpt_name)
2204{
2205 uint16_t i;
2206 struct nc_endpt *endpt = NULL;
2207 struct nc_bind *bind;
2208
romanb6ad37a2023-12-07 13:08:46 +01002209 NC_CHECK_ARG_RET(NULL, endpt_name, );
2210
romanfb3f7cf2023-11-30 16:10:09 +01002211 /* CONFIG LOCK */
2212 pthread_rwlock_wrlock(&server_opts.config_lock);
2213
romanfb3f7cf2023-11-30 16:10:09 +01002214 for (i = 0; i < server_opts.endpt_count; i++) {
2215 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2216 endpt = &server_opts.endpts[i];
2217 bind = &server_opts.binds[i];
2218 break;
2219 }
2220 }
2221 if (!endpt) {
2222 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2223 goto end;
2224 }
2225 if (endpt->ti != NC_TI_UNIX) {
2226 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2227 goto end;
2228 }
2229
2230 _nc_server_del_endpt_unix_socket(endpt, bind);
2231
2232end:
2233 /* CONFIG UNLOCK */
2234 pthread_rwlock_unlock(&server_opts.config_lock);
2235}
2236
2237API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002238nc_server_endpt_count(void)
2239{
2240 return server_opts.endpt_count;
2241}
2242
Michal Vasko71090fc2016-05-24 16:37:28 +02002243API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002244nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002245{
Michal Vasko71090fc2016-05-24 16:37:28 +02002246 NC_MSG_TYPE msgtype;
Michal Vasko87b42562024-05-03 12:02:01 +02002247 int sock = -1, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002248 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002249 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002250 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002251
romanf578cd52023-10-19 09:47:40 +02002252 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002253
romand82caf12024-03-05 14:21:39 +01002254 NC_CHECK_SRV_INIT_RET(NC_MSG_ERROR);
2255
2256 *session = NULL;
2257
Michal Vasko93224072021-11-09 12:14:28 +01002258 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002259 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002260
romanf578cd52023-10-19 09:47:40 +02002261 /* CONFIG LOCK */
2262 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002263
2264 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002265 ERR(NULL, "No endpoints to accept sessions on.");
Michal Vasko87b42562024-05-03 12:02:01 +02002266 msgtype = NC_MSG_ERROR;
2267 goto cleanup;
Michal Vasko51e514d2016-02-02 15:51:52 +01002268 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002269
romanf578cd52023-10-19 09:47:40 +02002270 ret = nc_sock_accept_binds(server_opts.binds, server_opts.endpt_count, &server_opts.bind_lock, timeout, &host, &port, &bind_idx);
Michal Vasko50456e82016-02-02 12:16:08 +01002271 if (ret < 1) {
Michal Vasko87b42562024-05-03 12:02:01 +02002272 msgtype = (!ret ? NC_MSG_WOULDBLOCK : NC_MSG_ERROR);
2273 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002274 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002275 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002276
Michal Vaskoc4cdc9e2024-05-03 12:03:07 +02002277 /* configure keepalives */
2278 if (nc_sock_configure_ka(sock, &server_opts.endpts[bind_idx].ka)) {
2279 msgtype = NC_MSG_ERROR;
2280 goto cleanup;
2281 }
2282
Michal Vasko131120a2018-05-29 15:44:02 +02002283 *session = nc_new_session(NC_SERVER, 0);
Michal Vasko87b42562024-05-03 12:02:01 +02002284 NC_CHECK_ERRMEM_GOTO(!(*session), msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002285 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002286 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002287 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002288 (*session)->host = host;
Michal Vasko87b42562024-05-03 12:02:01 +02002289 host = NULL;
Michal Vasko1a38c862016-01-15 15:50:07 +01002290 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002291
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002292 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002293#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002294 if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002295 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002296 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002297 if (ret < 0) {
2298 msgtype = NC_MSG_ERROR;
2299 goto cleanup;
2300 } else if (!ret) {
2301 msgtype = NC_MSG_WOULDBLOCK;
2302 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002303 }
romanf578cd52023-10-19 09:47:40 +02002304 } else if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002305 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002306 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko87b42562024-05-03 12:02:01 +02002307 sock = -1;
Michal Vasko71090fc2016-05-24 16:37:28 +02002308 if (ret < 0) {
2309 msgtype = NC_MSG_ERROR;
2310 goto cleanup;
2311 } else if (!ret) {
2312 msgtype = NC_MSG_WOULDBLOCK;
2313 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002314 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002315 } else
romanf578cd52023-10-19 09:47:40 +02002316#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002317 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2318 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2319 ret = nc_accept_unix(*session, sock);
Michal Vasko87b42562024-05-03 12:02:01 +02002320 sock = -1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002321 if (ret < 0) {
2322 msgtype = NC_MSG_ERROR;
2323 goto cleanup;
2324 }
2325 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002326 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02002327 msgtype = NC_MSG_ERROR;
2328 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002329 }
2330
Michal Vasko2cc4c682016-03-01 09:16:48 +01002331 (*session)->data = NULL;
2332
romanf578cd52023-10-19 09:47:40 +02002333 /* CONFIG UNLOCK */
2334 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002335
Michal Vaskob48aa812016-01-18 14:13:09 +01002336 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002337 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002338
Michal Vasko9e036d52016-01-08 10:49:26 +01002339 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002340 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002341 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002342 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002343 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002344 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002345 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002346
Michal Vaskod8a74192023-02-06 15:51:50 +01002347 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002348 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002349 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002350 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002351 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002352
Michal Vasko71090fc2016-05-24 16:37:28 +02002353 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002354
Michal Vasko71090fc2016-05-24 16:37:28 +02002355cleanup:
romanf578cd52023-10-19 09:47:40 +02002356 /* CONFIG UNLOCK */
2357 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002358
Michal Vasko87b42562024-05-03 12:02:01 +02002359 free(host);
2360 if (sock > -1) {
2361 close(sock);
2362 }
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002363 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002364 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002365 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002366}
2367
romanf578cd52023-10-19 09:47:40 +02002368#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002369
2370API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002371nc_server_ch_is_client(const char *name)
2372{
2373 uint16_t i;
2374 int found = 0;
2375
2376 if (!name) {
2377 return found;
2378 }
2379
2380 /* READ LOCK */
2381 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2382
2383 /* check name uniqueness */
2384 for (i = 0; i < server_opts.ch_client_count; ++i) {
2385 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2386 found = 1;
2387 break;
2388 }
2389 }
2390
2391 /* UNLOCK */
2392 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2393
2394 return found;
2395}
2396
2397API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002398nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2399{
2400 uint16_t i;
2401 struct nc_ch_client *client = NULL;
2402 int found = 0;
2403
2404 if (!client_name || !endpt_name) {
2405 return found;
2406 }
2407
2408 /* READ LOCK */
2409 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2410
2411 for (i = 0; i < server_opts.ch_client_count; ++i) {
2412 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2413 client = &server_opts.ch_clients[i];
2414 break;
2415 }
2416 }
2417
2418 if (!client) {
2419 goto cleanup;
2420 }
2421
2422 for (i = 0; i < client->ch_endpt_count; ++i) {
2423 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2424 found = 1;
2425 break;
2426 }
2427 }
2428
2429cleanup:
2430 /* UNLOCK */
2431 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2432 return found;
2433}
2434
Michal Vasko056f53c2022-10-21 13:38:15 +02002435/**
2436 * @brief Create a connection for an endpoint.
2437 *
2438 * Client lock is expected to be held.
2439 *
2440 * @param[in] endpt Endpoint to use.
2441 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2442 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2443 * @param[in] ctx_cb_data Context callbacks data.
2444 * @param[out] session Created NC session.
2445 * @return NC_MSG values.
2446 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002447static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002448nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2449 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 +01002450{
Michal Vasko71090fc2016-05-24 16:37:28 +02002451 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002452 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002453 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002454 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002455 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002456
Michal Vasko056f53c2022-10-21 13:38:15 +02002457 sock = nc_sock_connect(endpt->address, endpt->port, NC_CH_CONNECT_TIMEOUT, &endpt->ka, &endpt->sock_pending, &ip_host);
Michal Vaskoc61c4492016-01-25 11:13:34 +01002458 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002459 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002460 }
2461
Michal Vasko93224072021-11-09 12:14:28 +01002462 /* acquire context */
2463 ctx = acquire_ctx_cb(ctx_cb_data);
2464 if (!ctx) {
2465 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2466 close(sock);
2467 free(ip_host);
2468 return NC_MSG_ERROR;
2469 }
2470
romanf578cd52023-10-19 09:47:40 +02002471 /* init ctx as needed */
2472 nc_server_init_cb_ctx(ctx);
2473
Michal Vasko93224072021-11-09 12:14:28 +01002474 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002475 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002476 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002477 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002478 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002479 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002480 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002481 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002482
Michal Vaskob05053d2016-01-22 16:12:06 +01002483 /* sock gets assigned to session or closed */
Michal Vaskoadf30f02019-06-24 09:34:47 +02002484 if (endpt->ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002485 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002486 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002487
Michal Vasko71090fc2016-05-24 16:37:28 +02002488 if (ret < 0) {
2489 msgtype = NC_MSG_ERROR;
2490 goto fail;
2491 } else if (!ret) {
2492 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002493 goto fail;
2494 }
romanf578cd52023-10-19 09:47:40 +02002495 } else if (endpt->ti == NC_TI_OPENSSL) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002496 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002497 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002498 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002499
Michal Vasko71090fc2016-05-24 16:37:28 +02002500 if (ret < 0) {
2501 msgtype = NC_MSG_ERROR;
2502 goto fail;
2503 } else if (!ret) {
2504 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002505 goto fail;
2506 }
roman423cc0d2023-11-24 11:29:47 +01002507 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002508 ERRINT;
2509 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002510 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002511 goto fail;
2512 }
2513
2514 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002515 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002516
2517 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002518 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002519 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002520 goto fail;
2521 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002522
Michal Vaskod8a74192023-02-06 15:51:50 +01002523 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002524 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002525 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002526 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002527 (*session)->status = NC_STATUS_RUNNING;
2528
Michal Vasko71090fc2016-05-24 16:37:28 +02002529 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002530
2531fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002532 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002533 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002534 if (ctx) {
2535 release_ctx_cb(ctx_cb_data);
2536 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002537 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002538}
2539
Michal Vasko6f865982023-11-21 12:10:42 +01002540/**
2541 * @brief Wait for any event after a NC session was established on a CH client.
2542 *
Michal Vasko6f865982023-11-21 12:10:42 +01002543 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002544 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002545 * @return 0 if session was terminated normally,
2546 * @return 1 if the CH client was removed,
2547 * @return -1 on error.
2548 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002549static int
roman8341e8b2023-11-23 16:12:42 +01002550nc_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 +02002551{
Michal Vasko6f865982023-11-21 12:10:42 +01002552 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002553 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002554 struct timespec ts;
2555 struct nc_ch_client *client;
2556
Michal Vasko2e6defd2016-10-07 15:48:15 +02002557 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002558 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002559
Michal Vaskofeccb312022-03-24 15:24:59 +01002560 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002561
Michal Vasko2e6defd2016-10-07 15:48:15 +02002562 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002563 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002564 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002565 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002566
2567 /* CH UNLOCK */
2568 pthread_mutex_unlock(&session->opts.server.ch_lock);
2569
Michal Vasko77d56d72022-09-07 10:30:48 +02002570 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002571 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002572 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002573 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002574 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002575
2576 do {
romanf578cd52023-10-19 09:47:40 +02002577 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002578
Michal Vasko0db3db52021-03-03 10:45:42 +01002579 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002580 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 +01002581 if (!r) {
2582 /* we were woken up, something probably happened */
2583 if (session->status != NC_STATUS_RUNNING) {
2584 break;
2585 }
2586 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002587 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002588 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002589 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002590 }
2591
Michal Vasko2e6defd2016-10-07 15:48:15 +02002592 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002593
Michal Vasko2e6defd2016-10-07 15:48:15 +02002594 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002595 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002596 if (!client) {
2597 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002598 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002599 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002600 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002601 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002602 }
2603
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002604 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002605 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002606 } else {
2607 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002608 }
2609
Michal Vaskod8a74192023-02-06 15:51:50 +01002610 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002611 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 +02002612 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002613 session->status = NC_STATUS_INVALID;
2614 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2615 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002616
2617 /* UNLOCK */
2618 nc_server_ch_client_unlock(client);
2619
2620 } while (session->status == NC_STATUS_RUNNING);
2621
Michal Vaskofeccb312022-03-24 15:24:59 +01002622 /* signal to nc_session_free() that CH thread is terminating */
2623 session->flags &= ~NC_SESSION_CH_THREAD;
2624 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002625
Michal Vasko27377422018-03-15 08:59:35 +01002626 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002627 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002628
Michal Vasko6f865982023-11-21 12:10:42 +01002629 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002630}
2631
romanf578cd52023-10-19 09:47:40 +02002632/**
2633 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2634 *
2635 * @param[in] session An established session.
2636 * @param[in] data Call Home thread's data.
2637 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2638 *
2639 * @return 0 if the thread should stop running, 1 if it should continue.
2640 */
2641static int
2642nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2643{
2644 struct timespec ts;
2645 int ret = 0, thread_running;
2646
2647 /* COND LOCK */
2648 pthread_mutex_lock(&data->cond_lock);
2649 /* get reconnect timeout in ms */
2650 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2651 while (!ret && data->thread_running) {
2652 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2653 }
2654
2655 thread_running = data->thread_running;
2656 /* COND UNLOCK */
2657 pthread_mutex_unlock(&data->cond_lock);
2658
2659 if (!thread_running) {
2660 /* thread is terminating */
2661 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2662 ret = 0;
2663 } else if (ret == ETIMEDOUT) {
2664 /* time to reconnect */
2665 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2666 ret = 1;
2667 } else if (ret) {
2668 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2669 ret = 0;
2670 }
2671
2672 return ret;
2673}
2674
2675/**
2676 * @brief Checks if a Call Home thread should terminate.
2677 *
2678 * Checks the shared boolean variable thread_running. This should be done everytime
2679 * before entering a critical section.
2680 *
2681 * @param[in] data Call Home thread's data.
2682 *
2683 * @return 0 if the thread should stop running, -1 if it can continue.
2684 */
2685static int
2686nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2687{
2688 int ret = -1;
2689
2690 /* COND LOCK */
2691 pthread_mutex_lock(&data->cond_lock);
2692 if (!data->thread_running) {
2693 /* thread should stop running */
2694 ret = 0;
2695 }
2696 /* COND UNLOCK */
2697 pthread_mutex_unlock(&data->cond_lock);
2698
2699 return ret;
2700}
2701
Michal Vasko6f865982023-11-21 12:10:42 +01002702/**
2703 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2704 *
2705 * @param[in] name Name of the CH client.
2706 * @return Pointer to the CH client.
2707 */
2708static struct nc_ch_client *
2709nc_server_ch_client_with_endpt_lock(const char *name)
2710{
2711 struct nc_ch_client *client;
2712
2713 while (1) {
2714 /* LOCK */
2715 client = nc_server_ch_client_lock(name);
2716 if (!client) {
2717 return NULL;
2718 }
2719 if (client->ch_endpt_count) {
2720 return client;
2721 }
2722 /* no endpoints defined yet */
2723
2724 /* UNLOCK */
2725 nc_server_ch_client_unlock(client);
2726
2727 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2728 }
2729
2730 return NULL;
2731}
2732
2733/**
2734 * @brief Call Home client management thread.
2735 *
2736 * @param[in] arg CH client thread argument.
2737 * @return NULL.
2738 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002739static void *
2740nc_ch_client_thread(void *arg)
2741{
Michal Vasko6f865982023-11-21 12:10:42 +01002742 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002743 NC_MSG_TYPE msgtype;
2744 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002745 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002746 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002747 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002748 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002749 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002750 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002751
2752 /* LOCK */
2753 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002754 if (!client) {
2755 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2756 goto cleanup;
2757 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002758
2759 cur_endpt = &client->ch_endpts[0];
2760 cur_endpt_name = strdup(cur_endpt->name);
2761
Michal Vasko6f865982023-11-21 12:10:42 +01002762 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002763 if (!cur_attempts) {
2764 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2765 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002766
Michal Vasko6f865982023-11-21 12:10:42 +01002767 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 +02002768 if (msgtype == NC_MSG_HELLO) {
2769 /* UNLOCK */
2770 nc_server_ch_client_unlock(client);
2771
romanf578cd52023-10-19 09:47:40 +02002772 if (!nc_server_ch_client_thread_is_running(data)) {
2773 /* thread should stop running */
2774 goto cleanup;
2775 }
2776
2777 /* run while the session is established */
2778 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002779 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002780 goto cleanup;
2781 }
roman8341e8b2023-11-23 16:12:42 +01002782 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002783
roman8341e8b2023-11-23 16:12:42 +01002784 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002785 if (!nc_server_ch_client_thread_is_running(data)) {
2786 /* thread should stop running */
2787 goto cleanup;
2788 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002789
2790 /* LOCK */
2791 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002792 if (!client) {
2793 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2794 goto cleanup;
2795 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002796
2797 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002798 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002799 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002800 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002801 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002802 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002803 } else {
2804 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002805 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002806 }
2807
Michal Vasko2e6defd2016-10-07 15:48:15 +02002808 /* UNLOCK */
2809 nc_server_ch_client_unlock(client);
2810
romanf578cd52023-10-19 09:47:40 +02002811 /* wait for the timeout to elapse, so we can try to reconnect */
2812 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2813 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2814 goto cleanup;
2815 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002816
2817 /* LOCK */
2818 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002819 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002820 }
2821
2822 /* set next endpoint to try */
2823 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002824 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002825 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002826 /* we keep the current one but due to unlock/lock we have to find it again */
2827 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2828 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2829 break;
2830 }
2831 }
2832 if (next_endpt_index >= client->ch_endpt_count) {
2833 /* endpoint was removed, start with the first one */
2834 next_endpt_index = 0;
2835 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002836 } else {
2837 /* just get a random index */
2838 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002839 }
2840
Michal Vasko2e6defd2016-10-07 15:48:15 +02002841 } else {
romanf578cd52023-10-19 09:47:40 +02002842 /* session was not created, wait a little bit and try again */
2843 max_wait = client->max_wait;
2844
Michal Vasko6bb116b2016-10-26 13:53:46 +02002845 /* UNLOCK */
2846 nc_server_ch_client_unlock(client);
2847
romanf578cd52023-10-19 09:47:40 +02002848 /* wait for max_wait seconds */
2849 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2850 /* thread should stop running */
2851 goto cleanup;
2852 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002853
Michal Vasko6bb116b2016-10-26 13:53:46 +02002854 /* LOCK */
2855 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002856 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002857
Michal Vasko2e6defd2016-10-07 15:48:15 +02002858 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002859
2860 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002861 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2862 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002863 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002864 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002865 }
2866
Peter Feiged05f2252018-09-03 08:09:47 +00002867 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002868 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002869 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002870 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002871 cur_attempts = 0;
2872 } else if (cur_attempts == client->max_attempts) {
2873 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002874 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002875 data->client_name, cur_endpt_name, client->max_attempts);
2876
2877 /* clear a pending socket, if any */
2878 cur_endpt = &client->ch_endpts[next_endpt_index];
2879 if (cur_endpt->sock_pending > -1) {
2880 close(cur_endpt->sock_pending);
2881 cur_endpt->sock_pending = -1;
2882 }
2883
Peter Feiged05f2252018-09-03 08:09:47 +00002884 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002885 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002886 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002887 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002888 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002889 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002890 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002891 cur_attempts = 0;
2892 } /* else we keep the current one */
2893 }
Peter Feiged05f2252018-09-03 08:09:47 +00002894
2895 cur_endpt = &client->ch_endpts[next_endpt_index];
2896 free(cur_endpt_name);
2897 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002898 }
Michal Vasko6f865982023-11-21 12:10:42 +01002899
romanf578cd52023-10-19 09:47:40 +02002900 /* UNLOCK if we break out of the loop */
2901 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002902
2903cleanup:
romanf578cd52023-10-19 09:47:40 +02002904 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002905 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002906 free(data->client_name);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002907 pthread_mutex_lock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002908 pthread_cond_destroy(&data->cond);
Jan Kundrátfd3a01d2024-04-10 03:03:08 +02002909 pthread_mutex_unlock(&data->cond_lock);
roman8341e8b2023-11-23 16:12:42 +01002910 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002911 free(data);
2912 return NULL;
2913}
2914
2915API int
Michal Vasko93224072021-11-09 12:14:28 +01002916nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002917 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2918 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002919{
Michal Vasko6f865982023-11-21 12:10:42 +01002920 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002921 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002922 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002923 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002924
romanf578cd52023-10-19 09:47:40 +02002925 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2926
romand82caf12024-03-05 14:21:39 +01002927 NC_CHECK_SRV_INIT_RET(-1);
2928
Michal Vasko6f865982023-11-21 12:10:42 +01002929 /* LOCK */
2930 ch_client = nc_server_ch_client_lock(client_name);
2931 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002932 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002933 return -1;
2934 }
2935
Michal Vasko6f865982023-11-21 12:10:42 +01002936 /* create the thread argument */
2937 arg = calloc(1, sizeof *arg);
2938 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002939 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002940 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002941 arg->acquire_ctx_cb = acquire_ctx_cb;
2942 arg->release_ctx_cb = release_ctx_cb;
2943 arg->ctx_cb_data = ctx_cb_data;
2944 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002945 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002946 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002947 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002948
Michal Vasko6f865982023-11-21 12:10:42 +01002949 /* creating the thread */
2950 arg->thread_running = 1;
2951 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2952 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2953 rc = -1;
2954 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002955 }
Michal Vasko6f865982023-11-21 12:10:42 +01002956
Michal Vasko2e6defd2016-10-07 15:48:15 +02002957 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002958 ch_client->tid = tid;
2959 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002960 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002961
Michal Vasko6f865982023-11-21 12:10:42 +01002962cleanup:
2963 /* UNLOCK */
2964 nc_server_ch_client_unlock(ch_client);
2965
2966 if (arg) {
2967 free(arg->client_name);
2968 free(arg);
2969 }
2970 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002971}
2972
romanf578cd52023-10-19 09:47:40 +02002973#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002974
roman44efa322023-11-03 13:57:25 +01002975API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002976nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002977{
roman44efa322023-11-03 13:57:25 +01002978 struct timespec fail = {0};
2979
2980 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002981
2982 if (session->side != NC_SERVER) {
2983 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002984 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002985 }
2986
Michal Vasko2e6defd2016-10-07 15:48:15 +02002987 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002988}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002989
2990API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002991nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002992{
2993 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002994 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002995 return;
2996 }
2997
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002998 /* NTF STATUS LOCK */
2999 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3000
Michal Vasko71dbd772021-03-23 14:08:37 +01003001 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003002
3003 /* NTF STATUS UNLOCK */
3004 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01003005}
3006
3007API void
3008nc_session_dec_notif_status(struct nc_session *session)
3009{
3010 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003011 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01003012 return;
3013 }
3014
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003015 /* NTF STATUS LOCK */
3016 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3017
Michal Vasko71dbd772021-03-23 14:08:37 +01003018 if (session->opts.server.ntf_status) {
3019 --session->opts.server.ntf_status;
3020 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003021
3022 /* NTF STATUS UNLOCK */
3023 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003024}
3025
3026API int
3027nc_session_get_notif_status(const struct nc_session *session)
3028{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003029 uint32_t ntf_status;
3030
Michal Vasko3486a7c2017-03-03 13:28:07 +01003031 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003032 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003033 return 0;
3034 }
3035
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003036 /* NTF STATUS LOCK */
3037 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3038
3039 ntf_status = session->opts.server.ntf_status;
3040
3041 /* NTF STATUS UNLOCK */
3042 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3043
3044 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003045}