blob: 105bd76a9c79b5f508a58e71af1325c865526ca5 [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
7 * Copyright (c) 2015 - 2021 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 */
Olivier Matzac7fa2f2018-10-11 10:02:04 +020016#define _GNU_SOURCE /* signals, 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 Vaskob83a3fa2021-05-26 09:53:42 +020024#include <poll.h>
Michal Vaskob48aa812016-01-18 14:13:09 +010025#include <pthread.h>
Olivier Matzac7fa2f2018-10-11 10:02:04 +020026#include <pwd.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020027#include <signal.h>
28#include <stdint.h>
29#include <stdlib.h>
30#include <string.h>
31#include <sys/socket.h>
romanf578cd52023-10-19 09:47:40 +020032#include <sys/stat.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020033#include <sys/types.h>
34#include <sys/un.h>
35#include <time.h>
36#include <unistd.h>
Michal Vasko086311b2016-01-08 09:53:11 +010037
romanf578cd52023-10-19 09:47:40 +020038#ifdef NC_ENABLED_SSH_TLS
39#include <curl/curl.h>
40#endif
41
Michal Vasko7a20d2e2021-05-19 16:40:23 +020042#include "compat.h"
romanf578cd52023-10-19 09:47:40 +020043#include "config.h"
44#include "log_p.h"
45#include "messages_p.h"
46#include "messages_server.h"
47#include "server_config_p.h"
48#include "session.h"
49#include "session_p.h"
Michal Vasko086311b2016-01-08 09:53:11 +010050#include "session_server.h"
Michal Vasko0bdf70b2019-06-24 19:20:20 +020051#include "session_server_ch.h"
Michal Vasko086311b2016-01-08 09:53:11 +010052
Michal Vaskob48aa812016-01-18 14:13:09 +010053struct nc_server_opts server_opts = {
romanf578cd52023-10-19 09:47:40 +020054 .config_lock = PTHREAD_RWLOCK_INITIALIZER,
55 .ch_client_lock = PTHREAD_RWLOCK_INITIALIZER,
Michal Vaskocf898172024-01-15 15:04:28 +010056 .idle_timeout = 180, /**< default idle timeout (not in config for UNIX socket) */
Michal Vaskob48aa812016-01-18 14:13:09 +010057};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010058
fanchanghu966f2de2016-07-21 02:28:57 -040059static nc_rpc_clb global_rpc_clb = NULL;
60
roman423cc0d2023-11-24 11:29:47 +010061#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010062/**
63 * @brief Lock CH client structures for reading and lock the specific client.
64 *
65 * @param[in] name Name of the CH client.
66 * @return CH client, NULL if not found.
67 */
68static struct nc_ch_client *
69nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010070{
71 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020072 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020073
Michal Vasko6f865982023-11-21 12:10:42 +010074 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020075
Michal Vasko2e6defd2016-10-07 15:48:15 +020076 /* READ LOCK */
77 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
78
79 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020080 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020081 client = &server_opts.ch_clients[i];
82 break;
83 }
84 }
85
86 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020087 /* READ UNLOCK */
88 pthread_rwlock_unlock(&server_opts.ch_client_lock);
89 } else {
90 /* CH CLIENT LOCK */
91 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020092 }
93
Michal Vasko6f865982023-11-21 12:10:42 +010094 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020095}
96
Michal Vasko6f865982023-11-21 12:10:42 +010097/**
98 * @brief Unlock CH client strcutures and the specific client.
99 *
100 * @param[in] endpt Locked CH client structure.
101 */
102static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200103nc_server_ch_client_unlock(struct nc_ch_client *client)
104{
105 /* CH CLIENT UNLOCK */
106 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100107
108 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200109 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100110}
Michal Vasko086311b2016-01-08 09:53:11 +0100111
roman423cc0d2023-11-24 11:29:47 +0100112#endif /* NC_ENABLED_SSH_TLS */
113
roman78df0fa2023-11-02 10:33:57 +0100114int
115nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
116{
117 uint16_t i;
118
119 for (i = 0; i < server_opts.endpt_count; i++) {
120 if (!strcmp(name, server_opts.endpts[i].name)) {
121 *endpt = &server_opts.endpts[i];
122 return 0;
123 }
124 }
125
126 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
127 return 1;
128}
129
Michal Vasko1a38c862016-01-15 15:50:07 +0100130API void
131nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
132{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200133 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200134 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200135 return;
136 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200137 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100138 return;
139 }
140
Michal Vasko142cfea2017-08-07 10:12:11 +0200141 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
142 session->killed_by = 0;
143 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100144 session->term_reason = reason;
145}
146
Michal Vasko142cfea2017-08-07 10:12:11 +0200147API void
148nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
149{
150 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200151 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200152 return;
153 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200154 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200155 return;
156 }
157
158 session->killed_by = sid;
159}
160
161API void
162nc_session_set_status(struct nc_session *session, NC_STATUS status)
163{
164 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200165 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200166 return;
167 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200168 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200169 return;
170 }
171
172 session->status = status;
173}
174
romanf578cd52023-10-19 09:47:40 +0200175API int
176nc_server_init_ctx(struct ly_ctx **ctx)
177{
178 int new_ctx = 0, i, ret = 0;
179 struct lys_module *module;
180 /* all features */
181 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
182 /* all features (module has no features) */
183 const char *ietf_netconf_monitoring_features[] = {NULL};
184
185 NC_CHECK_ARG_RET(NULL, ctx, 1);
186
187 if (!*ctx) {
188 /* context not given, create a new one */
189 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
190 ERR(NULL, "Couldn't create new libyang context.\n");
191 ret = 1;
192 goto cleanup;
193 }
194 new_ctx = 1;
195 }
196
197 if (new_ctx) {
198 /* new context created, implement both modules */
199 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
200 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
201 ret = 1;
202 goto cleanup;
203 }
204
205 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
206 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
207 ret = 1;
208 goto cleanup;
209 }
210
211 goto cleanup;
212 }
213
214 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
215 if (module) {
216 /* ietf-netconf module is present, check features */
217 for (i = 0; ietf_netconf_features[i]; i++) {
218 if (lys_feature_value(module, ietf_netconf_features[i])) {
219 /* feature not found, enable all of them */
220 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
221 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
222 ret = 1;
223 goto cleanup;
224 }
225
226 break;
227 }
228 }
229 } else {
230 /* ietf-netconf module not found, add it */
231 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
232 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
233 ret = 1;
234 goto cleanup;
235 }
236 }
237
238 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
239 if (!module) {
240 /* ietf-netconf-monitoring module not found, add it */
241 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
242 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
243 ret = 1;
244 goto cleanup;
245 }
246 }
247
248cleanup:
249 if (new_ctx && ret) {
250 ly_ctx_destroy(*ctx);
251 *ctx = NULL;
252 }
253 return ret;
254}
255
roman96c27f92023-11-02 11:09:46 +0100256#ifdef NC_ENABLED_SSH_TLS
257
roman450c00b2023-11-02 10:31:45 +0100258API void
259nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
260 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
261 void *new_session_cb_data)
262{
263 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
264
265 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
266 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
267 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
268 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
269 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
270}
271
roman96c27f92023-11-02 11:09:46 +0100272#endif
273
Michal Vasko086311b2016-01-08 09:53:11 +0100274int
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200275nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka)
Michal Vasko086311b2016-01-08 09:53:11 +0100276{
Michal Vasko06c860d2018-07-09 16:08:52 +0200277 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100278 int is_ipv4, sock;
279 struct sockaddr_storage saddr;
280
281 struct sockaddr_in *saddr4;
282 struct sockaddr_in6 *saddr6;
283
Michal Vasko086311b2016-01-08 09:53:11 +0100284 if (!strchr(address, ':')) {
285 is_ipv4 = 1;
286 } else {
287 is_ipv4 = 0;
288 }
289
290 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
291 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200292 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100293 goto fail;
294 }
295
Michal Vaskobe52dc22018-10-17 09:28:17 +0200296 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200297 opt = 1;
298 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200299 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200300 goto fail;
301 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100302 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200303 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100304 goto fail;
305 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200306
romanf578cd52023-10-19 09:47:40 +0200307 if (nc_sock_configure_keepalive(sock, ka)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100308 goto fail;
309 }
310
Michal Vaskof22d5ff2020-04-15 11:10:27 +0200311 memset(&saddr, 0, sizeof(struct sockaddr_storage));
Michal Vasko086311b2016-01-08 09:53:11 +0100312 if (is_ipv4) {
313 saddr4 = (struct sockaddr_in *)&saddr;
314
315 saddr4->sin_family = AF_INET;
316 saddr4->sin_port = htons(port);
317
318 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200319 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100320 goto fail;
321 }
322
323 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200324 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100325 goto fail;
326 }
327
328 } else {
329 saddr6 = (struct sockaddr_in6 *)&saddr;
330
331 saddr6->sin6_family = AF_INET6;
332 saddr6->sin6_port = htons(port);
333
334 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200335 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100336 goto fail;
337 }
338
339 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200340 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100341 goto fail;
342 }
343 }
344
Michal Vaskofb89d772016-01-08 12:25:35 +0100345 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200346 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100347 goto fail;
348 }
Michal Vasko086311b2016-01-08 09:53:11 +0100349 return sock;
350
351fail:
352 if (sock > -1) {
353 close(sock);
354 }
355
356 return -1;
357}
358
Michal Vaskoc429a8e2024-01-15 15:04:57 +0100359/**
360 * @brief Create a listening socket (AF_UNIX).
361 *
362 * @param[in] opts The server options (unix permissions and address of the socket).
363 * @return Listening socket, -1 on error.
364 */
365static int
roman83683fb2023-02-24 09:15:23 +0100366nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200367{
368 struct sockaddr_un sun;
369 int sock = -1;
370
roman83683fb2023-02-24 09:15:23 +0100371 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
372 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 +0200373 goto fail;
374 }
375
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200376 sock = socket(AF_UNIX, SOCK_STREAM, 0);
377 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200378 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200379 goto fail;
380 }
381
382 memset(&sun, 0, sizeof(sun));
383 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100384 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200385
386 unlink(sun.sun_path);
387 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100388 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200389 goto fail;
390 }
391
392 if (opts->mode != (mode_t)-1) {
393 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200394 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200395 goto fail;
396 }
397 }
398
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200399 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200400 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200401 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200402 goto fail;
403 }
404 }
405
406 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100407 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200408 goto fail;
409 }
410
411 return sock;
412
413fail:
414 if (sock > -1) {
415 close(sock);
416 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200417 return -1;
418}
419
aPiecek90ff0242021-02-14 14:58:01 +0100420/**
421 * @brief Evaluate socket name for AF_UNIX socket.
422 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
423 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
424 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
425 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
426 * @return -1 in case of error. Parameter host is set to NULL.
427 */
428static int
429sock_host_unix(int acc_sock_fd, char **host)
430{
431 char *sun_path;
432 struct sockaddr_storage saddr;
433 socklen_t addr_len;
434
435 *host = NULL;
436 saddr.ss_family = AF_UNIX;
437 addr_len = sizeof(saddr);
438
439 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200440 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100441 return -1;
442 }
443
444 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
445 if (!sun_path) {
446 /* stream socket is unnamed */
447 return 0;
448 }
449
roman3a95bb22023-10-26 11:07:17 +0200450 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100451
452 return 0;
453}
454
455/**
456 * @brief Evaluate socket name and port number for AF_INET socket.
457 * @param[in] addr is pointing to structure filled by accept function which was successful.
458 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
459 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
460 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
461 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
462 */
463static int
464sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
465{
466 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200467 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100468
aPiecek3da9b342021-02-18 15:00:03 +0100469 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100470 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100471 free(*host);
472 *host = NULL;
473 return -1;
474 }
475
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200476 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100477
478 return 0;
479}
480
481/**
482 * @brief Evaluate socket name and port number for AF_INET6 socket.
483 * @param[in] addr is pointing to structure filled by accept function which was successful.
484 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
485 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
486 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
487 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
488 */
489static int
490sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
491{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200492 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200493 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100494
aPiecek3da9b342021-02-18 15:00:03 +0100495 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100496 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100497 free(*host);
498 *host = NULL;
499 return -1;
500 }
501
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200502 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100503
504 return 0;
505}
506
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200507int
Michal Vasko6f865982023-11-21 12:10:42 +0100508nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
509 uint16_t *port, uint16_t *idx)
Michal Vasko086311b2016-01-08 09:53:11 +0100510{
Michal Vaskof54cd352017-02-22 13:42:02 +0100511 sigset_t sigmask, origmask;
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200512 uint16_t i, j, pfd_count, client_port;
513 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100514 struct pollfd *pfd;
515 struct sockaddr_storage saddr;
516 socklen_t saddr_len = sizeof(saddr);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200517 int ret, client_sock, sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100518
519 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200520 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100521
romanf578cd52023-10-19 09:47:40 +0200522 /* LOCK */
523 pthread_mutex_lock(bind_lock);
524
Michal Vaskoac2f6182017-01-30 14:32:03 +0100525 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200526 if (binds[i].sock < 0) {
527 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200528 continue;
529 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200530 if (binds[i].pollin) {
531 binds[i].pollin = 0;
532 /* leftover pollin */
533 sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100534 break;
535 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100536 pfd[pfd_count].fd = binds[i].sock;
537 pfd[pfd_count].events = POLLIN;
538 pfd[pfd_count].revents = 0;
539
540 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100541 }
542
Michal Vasko0a3f3752016-10-13 14:58:38 +0200543 if (sock == -1) {
544 /* poll for a new connection */
Michal Vaskof54cd352017-02-22 13:42:02 +0100545 sigfillset(&sigmask);
546 pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
Michal Vaskoac2f6182017-01-30 14:32:03 +0100547 ret = poll(pfd, pfd_count, timeout);
Michal Vaskof54cd352017-02-22 13:42:02 +0100548 pthread_sigmask(SIG_SETMASK, &origmask, NULL);
549
Michal Vasko0a3f3752016-10-13 14:58:38 +0200550 if (!ret) {
551 /* we timeouted */
552 free(pfd);
romanf578cd52023-10-19 09:47:40 +0200553 /* UNLOCK */
554 pthread_mutex_unlock(bind_lock);
Michal Vasko0a3f3752016-10-13 14:58:38 +0200555 return 0;
556 } else if (ret == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200557 ERR(NULL, "Poll failed (%s).", strerror(errno));
Michal Vasko0a3f3752016-10-13 14:58:38 +0200558 free(pfd);
romanf578cd52023-10-19 09:47:40 +0200559 /* UNLOCK */
560 pthread_mutex_unlock(bind_lock);
Michal Vasko0a3f3752016-10-13 14:58:38 +0200561 return -1;
562 }
Michal Vasko086311b2016-01-08 09:53:11 +0100563
Michal Vaskoac2f6182017-01-30 14:32:03 +0100564 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
565 /* adjust i so that indices in binds and pfd always match */
566 while (binds[i].sock != pfd[j].fd) {
567 ++i;
568 }
569
570 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200571 --ret;
572
573 if (!ret) {
574 /* the last socket with an event, use it */
Michal Vaskoac2f6182017-01-30 14:32:03 +0100575 sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200576 break;
577 } else {
578 /* just remember the event for next time */
579 binds[i].pollin = 1;
580 }
581 }
Michal Vasko086311b2016-01-08 09:53:11 +0100582 }
583 }
584 free(pfd);
Michal Vasko086311b2016-01-08 09:53:11 +0100585 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100586 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200587 /* UNLOCK */
588 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100589 return -1;
590 }
591
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200592 /* accept connection */
593 client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
594 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200595 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200596 /* UNLOCK */
597 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100598 return -1;
599 }
600
Michal Vasko0190bc32016-03-02 15:47:49 +0100601 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200602 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200603 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200604 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100605 }
606
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200607 /* learn information about the client end */
608 if (saddr.ss_family == AF_UNIX) {
609 if (sock_host_unix(client_sock, &client_address)) {
610 goto fail;
611 }
612 client_port = 0;
613 } else if (saddr.ss_family == AF_INET) {
614 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
615 goto fail;
616 }
617 } else if (saddr.ss_family == AF_INET6) {
618 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
619 goto fail;
620 }
621 } else {
622 ERR(NULL, "Source host of an unknown protocol family.");
623 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100624 }
Michal Vasko086311b2016-01-08 09:53:11 +0100625
aPiecek90ff0242021-02-14 14:58:01 +0100626 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200627 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100628 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200629 VRB(NULL, "Accepted a connection on %s:%u from %s:%u.", binds[i].address, binds[i].port, client_address, client_port);
Michal Vasko086311b2016-01-08 09:53:11 +0100630 }
631
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200632 if (host) {
633 *host = client_address;
634 } else {
635 free(client_address);
636 }
637 if (port) {
638 *port = client_port;
639 }
640 if (idx) {
641 *idx = i;
642 }
romanf578cd52023-10-19 09:47:40 +0200643 /* UNLOCK */
644 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200645 return client_sock;
646
647fail:
648 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200649 /* UNLOCK */
650 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200651 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100652}
653
Michal Vasko238b6c12021-12-14 15:14:09 +0100654API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200655nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100656{
Michal Vasko77367452021-02-16 16:32:18 +0100657 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100658 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100659 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100660 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100661 const struct lysp_submodule *submodule = NULL;
662 struct lyd_node *child, *err, *data = NULL;
663 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100664
Michal Vasko77367452021-02-16 16:32:18 +0100665 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100666 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200667 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100668 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200669 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200670 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100671 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500672 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100673 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200674 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100675 }
676 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200677 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100678
Michal Vasko77367452021-02-16 16:32:18 +0100679 /* check revision */
680 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100681 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100682 nc_err_set_msg(err, "The requested version is not supported.", "en");
683 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100684 }
685
Michal Vasko77367452021-02-16 16:32:18 +0100686 if (revision) {
687 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100688 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100689 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100690 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100691 }
692 } else {
693 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100694 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100695 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100696 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100697 }
698 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100699 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100700 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200701 }
Michal Vasko77367452021-02-16 16:32:18 +0100702 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100703 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200704 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100705 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100706 }
707
708 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100709 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100710 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100711 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100712 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100713 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100714 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100715 nc_err_set_msg(err, "The requested format is not supported.", "en");
716 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100717 }
Michal Vasko77367452021-02-16 16:32:18 +0100718
719 /* print */
720 ly_out_new_memory(&model_data, 0, &out);
721 if (module) {
722 lys_print_module(out, module, outformat, 0, 0);
723 } else {
724 lys_print_submodule(out, submodule, outformat, 0, 0);
725 }
726 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200727 if (!model_data) {
728 ERRINT;
729 return NULL;
730 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100731
Michal Vasko9b1a9522021-03-15 16:24:26 +0100732 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100733 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100734 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100735 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200736 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100737 return NULL;
738 }
Michal Vasko9b1a9522021-03-15 16:24:26 +0100739 if (lyd_new_any(data, NULL, "data", model_data, 1, LYD_ANYDATA_STRING, 1, NULL)) {
740 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100741 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100742 lyd_free_tree(data);
743 return NULL;
744 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100745
Radek Krejci36dfdb32016-09-01 16:56:35 +0200746 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100747}
748
Michal Vasko238b6c12021-12-14 15:14:09 +0100749API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100750nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100751{
Michal Vasko428087d2016-01-14 16:04:28 +0100752 session->term_reason = NC_SESSION_TERM_CLOSED;
753 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100754}
755
Michal Vasko93224072021-11-09 12:14:28 +0100756/**
757 * @brief Initialize a context with default RPC callbacks if none are set.
758 *
759 * @param[in] ctx Context to initialize.
760 */
761static void
romanf578cd52023-10-19 09:47:40 +0200762nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100763{
Michal Vasko77367452021-02-16 16:32:18 +0100764 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100765
Michal Vasko238b6c12021-12-14 15:14:09 +0100766 if (global_rpc_clb) {
767 /* expect it to handle these RPCs as well */
768 return;
769 }
770
Michal Vasko05ba9df2016-01-13 14:40:27 +0100771 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100772 rpc = NULL;
773 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
774 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
775 }
Michal Vasko88639e92017-08-03 14:38:10 +0200776 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100777 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100778 }
779
Michal Vasko93224072021-11-09 12:14:28 +0100780 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100781 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200782 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100783 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100784 }
Michal Vasko93224072021-11-09 12:14:28 +0100785}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100786
Michal Vasko93224072021-11-09 12:14:28 +0100787API int
788nc_server_init(void)
789{
790 pthread_rwlockattr_t attr, *attr_p = NULL;
791 int r;
792
Michal Vaskob48aa812016-01-18 14:13:09 +0100793 server_opts.new_session_id = 1;
Andrew Langefeld6ed922d2018-09-12 14:08:32 -0500794 server_opts.new_client_id = 1;
Michal Vaskob48aa812016-01-18 14:13:09 +0100795
Michal Vasko93224072021-11-09 12:14:28 +0100796#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
797 if ((r = pthread_rwlockattr_init(&attr))) {
798 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
799 goto error;
800 }
801 attr_p = &attr;
802 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
803 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
804 goto error;
805 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700806#endif
Michal Vasko93224072021-11-09 12:14:28 +0100807
romanf578cd52023-10-19 09:47:40 +0200808 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100809 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
810 goto error;
811 }
812 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
813 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
814 goto error;
815 }
816
817 if (attr_p) {
818 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000819 }
romanf578cd52023-10-19 09:47:40 +0200820
821#ifdef NC_ENABLED_SSH_TLS
822 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
823 ERR(NULL, "%s: failed to init CURL.", __func__);
824 goto error;
825 }
826#endif
827
828 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
829 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
830 goto error;
831 }
832
Michal Vasko086311b2016-01-08 09:53:11 +0100833 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100834
835error:
836 if (attr_p) {
837 pthread_rwlockattr_destroy(attr_p);
838 }
839 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100840}
841
Michal Vaskob48aa812016-01-18 14:13:09 +0100842API void
843nc_server_destroy(void)
844{
romana2ff4ef2024-01-19 14:41:46 +0100845 uint32_t i, endpt_count;
Radek Krejci658782b2016-12-04 22:04:55 +0100846
847 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100848 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100849 }
850 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200851 server_opts.capabilities = NULL;
852 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200853 if (server_opts.content_id_data && server_opts.content_id_data_free) {
854 server_opts.content_id_data_free(server_opts.content_id_data);
855 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200856
romanf578cd52023-10-19 09:47:40 +0200857 nc_server_config_listen(NULL, NC_OP_DELETE);
858 nc_server_config_ch(NULL, NC_OP_DELETE);
859
romana2ff4ef2024-01-19 14:41:46 +0100860 endpt_count = server_opts.endpt_count;
861 for (i = 0; i < endpt_count; i++) {
862 if (server_opts.endpts[i].ti == NC_TI_UNIX) {
863 _nc_server_del_endpt_unix_socket(&server_opts.endpts[i], &server_opts.binds[i]);
864 }
865 }
866
romanf578cd52023-10-19 09:47:40 +0200867 pthread_mutex_destroy(&server_opts.bind_lock);
868
869#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100870 free(server_opts.authkey_path_fmt);
871 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100872 free(server_opts.pam_config_name);
873 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200874 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
875 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
876 }
877 server_opts.interactive_auth_data = NULL;
878 server_opts.interactive_auth_data_free = NULL;
879
romanf578cd52023-10-19 09:47:40 +0200880 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
881 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
882 curl_global_cleanup();
883#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100884}
885
Michal Vasko086311b2016-01-08 09:53:11 +0100886API int
887nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
888{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200889 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200890 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200891 return -1;
892 } 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 +0200893 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100894 return -1;
895 }
896
romanf578cd52023-10-19 09:47:40 +0200897 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
898 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100899 return 0;
900}
901
Michal Vasko1a38c862016-01-15 15:50:07 +0100902API void
Michal Vasko55f03972016-04-13 08:56:01 +0200903nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
904{
905 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200906 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200907 return;
908 }
909
910 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200911 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200912 }
913 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200914 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200915 }
916}
917
Michal Vasko55f03972016-04-13 08:56:01 +0200918API int
Radek Krejci658782b2016-12-04 22:04:55 +0100919nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200920{
Michal Vasko93224072021-11-09 12:14:28 +0100921 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100922
923 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200924 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100925 return EXIT_FAILURE;
926 }
927
Michal Vasko93224072021-11-09 12:14:28 +0100928 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200929 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100930 server_opts.capabilities = mem;
931
932 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
933 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100934
935 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200936}
937
Michal Vasko1a38c862016-01-15 15:50:07 +0100938API void
Michal Vasko1440a742021-03-31 11:11:03 +0200939nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
940 void (*free_user_data)(void *user_data))
941{
942 server_opts.content_id_clb = content_id_clb;
943 server_opts.content_id_data = user_data;
944 server_opts.content_id_data_free = free_user_data;
945}
946
Michal Vasko71090fc2016-05-24 16:37:28 +0200947API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100948nc_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 +0100949{
Michal Vasko71090fc2016-05-24 16:37:28 +0200950 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200951 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200952
romanf578cd52023-10-19 09:47:40 +0200953 NC_CHECK_ARG_RET(NULL, ctx, username, session, NC_MSG_ERROR);
954
955 if (fdin < 0) {
956 ERRARG(NULL, "fdin");
Michal Vasko71090fc2016-05-24 16:37:28 +0200957 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +0200958 } else if (fdout < 0) {
romanf578cd52023-10-19 09:47:40 +0200959 ERRARG(NULL, "fdout");
Michal Vasko71090fc2016-05-24 16:37:28 +0200960 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +0100961 }
962
Michal Vasko93224072021-11-09 12:14:28 +0100963 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200964 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100965
Michal Vasko086311b2016-01-08 09:53:11 +0100966 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200967 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200968 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100969 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100970
Michal Vasko086311b2016-01-08 09:53:11 +0100971 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100972 (*session)->ti_type = NC_TI_FD;
973 (*session)->ti.fd.in = fdin;
974 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100975
Michal Vasko93224072021-11-09 12:14:28 +0100976 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100977 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100978 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100979
Michal Vaskob48aa812016-01-18 14:13:09 +0100980 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200981 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100982
Michal Vasko086311b2016-01-08 09:53:11 +0100983 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200984 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200985 if (msgtype != NC_MSG_HELLO) {
986 nc_session_free(*session, NULL);
987 *session = NULL;
988 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100989 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200990
Michal Vaskod8a74192023-02-06 15:51:50 +0100991 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200992 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100993 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100994 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200995
Michal Vasko1a38c862016-01-15 15:50:07 +0100996 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100997
Michal Vasko71090fc2016-05-24 16:37:28 +0200998 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100999}
Michal Vasko9e036d52016-01-08 10:49:26 +01001000
Michal Vaskob30b99c2016-07-26 11:35:43 +02001001static void
Michal Vasko74c345f2018-02-07 10:37:11 +01001002nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
1003{
1004 uint8_t q_last;
1005
1006 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
1007 ERRINT;
1008 return;
1009 }
1010
1011 /* get a unique queue value (by adding 1 to the last added value, if any) */
1012 if (ps->queue_len) {
1013 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1014 *id = ps->queue[q_last] + 1;
1015 } else {
1016 *id = 0;
1017 }
1018
1019 /* add the id into the queue */
1020 ++ps->queue_len;
1021 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1022 ps->queue[q_last] = *id;
1023}
1024
1025static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001026nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1027{
Michal Vasko74c345f2018-02-07 10:37:11 +01001028 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001029
1030 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001031 /* get the actual queue idx */
1032 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001033
1034 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001035 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001036 /* another equal value, simply cannot be */
1037 ERRINT;
1038 }
Michal Vaskod8340032018-02-12 14:41:00 +01001039 if (found == 2) {
1040 /* move the following values */
1041 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1042 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001043 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001044 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001045 if (i == 0) {
1046 found = 1;
1047 } else {
1048 /* this is not okay, our id is in the middle of the queue */
1049 found = 2;
1050 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001051 }
1052 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001053 if (!found) {
1054 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001055 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001056 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001057
Michal Vasko103fe632018-02-12 16:37:45 +01001058 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001059 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001060 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001061 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1062 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001063}
1064
Michal Vaskof04a52a2016-04-07 10:52:10 +02001065int
Michal Vasko26043172016-07-26 14:08:59 +02001066nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001067{
1068 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001069 struct timespec ts;
1070
Michal Vaskobe86fe32016-04-07 10:43:03 +02001071 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001072 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001073 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001074 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001075 return -1;
1076 }
1077
Michal Vasko74c345f2018-02-07 10:37:11 +01001078 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001079 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001080 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001081 pthread_mutex_unlock(&ps->lock);
1082 return -1;
1083 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001084
1085 /* add ourselves into the queue */
1086 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001087 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 +02001088 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001089
1090 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001091 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001092 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001093
Michal Vaskod8a74192023-02-06 15:51:50 +01001094 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001095 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301096 /**
1097 * This may happen when another thread releases the lock and broadcasts the condition
1098 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1099 * but when actually this thread was ready for condition.
1100 */
preetbhansali629dfc42018-12-17 16:04:40 +05301101 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301102 break;
1103 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001104
Michal Vasko05532772021-06-03 12:12:38 +02001105 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001106 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001107 nc_ps_queue_remove_id(ps, *id);
1108 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001109 return -1;
1110 }
1111 }
1112
Michal Vaskobe86fe32016-04-07 10:43:03 +02001113 /* UNLOCK */
1114 pthread_mutex_unlock(&ps->lock);
1115
1116 return 0;
1117}
1118
Michal Vaskof04a52a2016-04-07 10:52:10 +02001119int
Michal Vasko26043172016-07-26 14:08:59 +02001120nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001121{
1122 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001123
1124 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001125 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001126 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001127 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001128 ret = -1;
1129 }
1130
Michal Vaskob30b99c2016-07-26 11:35:43 +02001131 /* we must be the first, it was our turn after all, right? */
1132 if (ps->queue[ps->queue_begin] != id) {
1133 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001134 /* UNLOCK */
1135 if (!ret) {
1136 pthread_mutex_unlock(&ps->lock);
1137 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001138 return -1;
1139 }
1140
Michal Vaskobe86fe32016-04-07 10:43:03 +02001141 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001142 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001143 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 +02001144 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001145
1146 /* broadcast to all other threads that the queue moved */
1147 pthread_cond_broadcast(&ps->cond);
1148
Michal Vaskobe86fe32016-04-07 10:43:03 +02001149 /* UNLOCK */
1150 if (!ret) {
1151 pthread_mutex_unlock(&ps->lock);
1152 }
1153
1154 return ret;
1155}
1156
Michal Vasko428087d2016-01-14 16:04:28 +01001157API struct nc_pollsession *
1158nc_ps_new(void)
1159{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001160 struct nc_pollsession *ps;
1161
1162 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001163 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001164 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001165 pthread_mutex_init(&ps->lock, NULL);
1166
1167 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001168}
1169
1170API void
1171nc_ps_free(struct nc_pollsession *ps)
1172{
fanchanghu3d4e7212017-08-09 09:42:30 +08001173 uint16_t i;
1174
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001175 if (!ps) {
1176 return;
1177 }
1178
Michal Vaskobe86fe32016-04-07 10:43:03 +02001179 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001180 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001181 }
1182
fanchanghu3d4e7212017-08-09 09:42:30 +08001183 for (i = 0; i < ps->session_count; i++) {
1184 free(ps->sessions[i]);
1185 }
1186
Michal Vasko428087d2016-01-14 16:04:28 +01001187 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001188 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001189 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001190
Michal Vasko428087d2016-01-14 16:04:28 +01001191 free(ps);
1192}
1193
1194API int
1195nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1196{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001197 uint8_t q_id;
1198
romanf578cd52023-10-19 09:47:40 +02001199 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001200
Michal Vasko48a63ed2016-03-01 09:48:21 +01001201 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001202 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001203 return -1;
1204 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001205
Michal Vasko428087d2016-01-14 16:04:28 +01001206 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001207 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001208 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001209 ERRMEM;
1210 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001211 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001212 return -1;
1213 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001214 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1215 if (!ps->sessions[ps->session_count - 1]) {
1216 ERRMEM;
1217 --ps->session_count;
1218 /* UNLOCK */
1219 nc_ps_unlock(ps, q_id, __func__);
1220 return -1;
1221 }
1222 ps->sessions[ps->session_count - 1]->session = session;
1223 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001224
Michal Vasko48a63ed2016-03-01 09:48:21 +01001225 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001226 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001227}
1228
Michal Vasko48a63ed2016-03-01 09:48:21 +01001229static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001230_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001231{
1232 uint16_t i;
1233
Radek Krejcid5f978f2016-03-03 13:14:45 +01001234 if (index >= 0) {
1235 i = (uint16_t)index;
1236 goto remove;
1237 }
Michal Vasko428087d2016-01-14 16:04:28 +01001238 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001239 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001240remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001241 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001242 if (i <= ps->session_count) {
1243 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001244 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001245 }
1246 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001247 free(ps->sessions);
1248 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001249 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001250 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001251 return 0;
1252 }
1253 }
1254
Michal Vaskof0537d82016-01-29 14:42:38 +01001255 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001256}
1257
Michal Vasko48a63ed2016-03-01 09:48:21 +01001258API int
1259nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1260{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001261 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001262 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001263
romanf578cd52023-10-19 09:47:40 +02001264 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001265
1266 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001267 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001268 return -1;
1269 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001270
Radek Krejcid5f978f2016-03-03 13:14:45 +01001271 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001272
1273 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001274 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001275
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001276 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001277}
1278
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001279API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001280nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001281{
1282 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001283 struct nc_session *ret = NULL;
1284
romanf578cd52023-10-19 09:47:40 +02001285 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001286
1287 /* LOCK */
1288 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1289 return NULL;
1290 }
1291
Michal Vasko4871c9d2017-10-09 14:48:39 +02001292 if (idx < ps->session_count) {
1293 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001294 }
1295
1296 /* UNLOCK */
1297 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1298
1299 return ret;
1300}
1301
Michal Vasko3ec3b112022-07-21 12:32:33 +02001302API struct nc_session *
1303nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1304{
1305 uint8_t q_id;
1306 uint16_t i;
1307 struct nc_session *ret = NULL;
1308
romanf578cd52023-10-19 09:47:40 +02001309 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001310
1311 /* LOCK */
1312 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1313 return NULL;
1314 }
1315
1316 for (i = 0; i < ps->session_count; ++i) {
1317 if (match_cb(ps->sessions[i]->session, cb_data)) {
1318 ret = ps->sessions[i]->session;
1319 break;
1320 }
1321 }
1322
1323 /* UNLOCK */
1324 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1325
1326 return ret;
1327}
1328
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001329API uint16_t
1330nc_ps_session_count(struct nc_pollsession *ps)
1331{
Michal Vasko47003942019-03-14 12:25:23 +01001332 uint8_t q_id;
1333 uint16_t session_count;
1334
romanf578cd52023-10-19 09:47:40 +02001335 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001336
Michal Vasko47003942019-03-14 12:25:23 +01001337 /* LOCK (just for memory barrier so that we read the current value) */
1338 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1339 return 0;
1340 }
1341
1342 session_count = ps->session_count;
1343
1344 /* UNLOCK */
1345 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1346
1347 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001348}
1349
Michal Vasko77e83572022-07-21 15:31:15 +02001350static NC_MSG_TYPE
1351recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1352{
1353 struct lyd_attr *attr;
1354
1355 assert(envp && !envp->schema);
1356
1357 /* find the message-id attribute */
1358 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1359 if (!strcmp(attr->name.name, "message-id")) {
1360 break;
1361 }
1362 }
1363
1364 if (!attr) {
1365 ERR(session, "Received an <rpc> without a message-id.");
1366 return NC_MSG_REPLY_ERR_MSGID;
1367 }
1368
1369 return NC_MSG_RPC;
1370}
1371
Michal Vasko131120a2018-05-29 15:44:02 +02001372/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001373 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001374 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001375 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001376 * NC_PSPOLL_RPC
1377 */
1378static int
Michal Vasko131120a2018-05-29 15:44:02 +02001379nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001380{
Michal Vasko77367452021-02-16 16:32:18 +01001381 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001382 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001383 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001384 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001385
romanf578cd52023-10-19 09:47:40 +02001386 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1387
1388 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001389 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001390 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001391 }
1392
Michal Vasko93224072021-11-09 12:14:28 +01001393 *rpc = NULL;
1394
Michal Vasko77367452021-02-16 16:32:18 +01001395 /* get a message */
1396 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1397 if (r == -2) {
1398 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001399 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001400 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001401 }
1402 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001403 return NC_PSPOLL_ERROR;
1404 } else if (!r) {
1405 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001406 }
1407
Michal Vasko77367452021-02-16 16:32:18 +01001408 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001409 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001410
1411 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001412 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1413 /* check message-id */
1414 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1415 /* valid RPC */
1416 ret = NC_PSPOLL_RPC;
1417 } else {
1418 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001419 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1420 }
1421 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001422 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001423 if ((*rpc)->envp) {
1424 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001425 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1426 nc_err_set_msg(e, ly_errmsg(session->ctx), "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001427 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001428 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001429 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1430 * the server (RFC 6241 sec. 3) */
1431 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001432 } else {
1433 /* at least set the return value */
1434 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001435 }
Michal Vasko77367452021-02-16 16:32:18 +01001436 }
1437
1438cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001439 if (reply) {
1440 /* send error reply */
1441 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1442 nc_server_reply_free(reply);
1443 if (r != NC_MSG_REPLY) {
1444 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1445 if (session->status != NC_STATUS_INVALID) {
1446 session->status = NC_STATUS_INVALID;
1447 session->term_reason = NC_SESSION_TERM_OTHER;
1448 }
1449 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001450
1451 /* bad RPC and an error reply sent */
1452 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001453 }
1454
Michal Vasko77367452021-02-16 16:32:18 +01001455 ly_in_free(msg, 1);
1456 if (ret != NC_PSPOLL_RPC) {
1457 nc_server_rpc_free(*rpc);
1458 *rpc = NULL;
1459 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001460 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001461}
1462
fanchanghu966f2de2016-07-21 02:28:57 -04001463API void
1464nc_set_global_rpc_clb(nc_rpc_clb clb)
1465{
1466 global_rpc_clb = clb;
1467}
1468
Radek Krejci93e80222016-10-03 13:34:25 +02001469API NC_MSG_TYPE
1470nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1471{
Michal Vasko131120a2018-05-29 15:44:02 +02001472 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001473
1474 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001475 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001476 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001477 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001478 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001479 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001480 return NC_MSG_ERROR;
1481 }
1482
Michal Vasko131120a2018-05-29 15:44:02 +02001483 /* we do not need RPC lock for this, IO lock will be acquired properly */
1484 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001485 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001486 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001487 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001488
Michal Vasko131120a2018-05-29 15:44:02 +02001489 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001490}
1491
Michal Vaskof9467762023-03-28 09:02:08 +02001492/**
1493 * @brief Send a reply acquiring IO lock as needed.
1494 * Session RPC lock must be held!
1495 *
1496 * @param[in] session Session to use.
1497 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1498 * @param[in] rpc RPC to sent.
1499 * @return 0 on success.
1500 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1501 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001502 */
1503static int
Michal Vasko93224072021-11-09 12:14:28 +01001504nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001505{
1506 nc_rpc_clb clb;
1507 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001508 const struct lysc_node *rpc_act = NULL;
1509 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001510 int ret = 0;
1511 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001512
Michal Vasko4a827e52016-03-03 10:59:00 +01001513 if (!rpc) {
1514 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001515 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001516 }
1517
Michal Vasko77367452021-02-16 16:32:18 +01001518 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001519 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001520 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001521 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001522 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001523 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001524 if (elem->schema->nodetype == LYS_ACTION) {
1525 rpc_act = elem->schema;
1526 break;
1527 }
Michal Vasko77367452021-02-16 16:32:18 +01001528 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001529 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001530 if (!rpc_act) {
1531 ERRINT;
1532 return NC_PSPOLL_ERROR;
1533 }
1534 }
1535
1536 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001537 if (!global_rpc_clb) {
1538 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001539 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 +03001540 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001541 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001542 }
Michal Vasko428087d2016-01-14 16:04:28 +01001543 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001544 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001545 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001546 }
1547
1548 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001549 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001550 }
Michal Vasko77367452021-02-16 16:32:18 +01001551 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001552 if (reply->type == NC_RPL_ERROR) {
1553 ret |= NC_PSPOLL_REPLY_ERROR;
1554 }
1555 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001556
Michal Vasko131120a2018-05-29 15:44:02 +02001557 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001558 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001559 ret |= NC_PSPOLL_ERROR;
1560 }
Michal Vasko428087d2016-01-14 16:04:28 +01001561
1562 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1563 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1564 session->status = NC_STATUS_INVALID;
1565 }
1566
Michal Vasko71090fc2016-05-24 16:37:28 +02001567 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001568}
1569
Michal Vaskof9467762023-03-28 09:02:08 +02001570/**
1571 * @brief Poll a session from pspoll acquiring IO lock as needed.
1572 * Session must be running and session RPC lock held!
1573 *
1574 * @param[in] session Session to use.
1575 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1576 * @param[in] now_mono Current monotonic timestamp.
1577 * @param[in,out] msg Message to fill in case of an error.
1578 * @return NC_PSPOLL_RPC if some application data are available.
1579 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1580 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1581 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1582 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1583 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001584 */
1585static int
Michal Vasko131120a2018-05-29 15:44:02 +02001586nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001587{
Michal Vasko9a327362017-01-11 11:31:46 +01001588 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001589 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001590
romanf578cd52023-10-19 09:47:40 +02001591#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001592 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001593 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001594#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001595
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001596 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001597 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001598 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001599 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001600 session->status = NC_STATUS_INVALID;
1601 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1602 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1603 }
1604
Michal Vasko131120a2018-05-29 15:44:02 +02001605 r = nc_session_io_lock(session, io_timeout, __func__);
1606 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001607 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001608 return NC_PSPOLL_ERROR;
1609 } else if (!r) {
1610 return NC_PSPOLL_TIMEOUT;
1611 }
1612
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001613 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001614#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001615 case NC_TI_LIBSSH:
romanf578cd52023-10-19 09:47:40 +02001616 ssh_msg = ssh_message_get(session->ti.libssh.session);
1617 if (ssh_msg) {
1618 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1619 if (session->ti.libssh.next) {
1620 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1621 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1622 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1623 /* new NETCONF SSH channel */
1624 ret = NC_PSPOLL_SSH_CHANNEL;
1625 break;
1626 }
1627 }
1628 if (new != session) {
1629 ssh_message_free(ssh_msg);
1630 break;
1631 }
1632 }
1633 if (!ret) {
1634 /* just some SSH message */
1635 ret = NC_PSPOLL_SSH_MSG;
1636 }
1637 ssh_message_free(ssh_msg);
1638
1639 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1640 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1641 */
1642 break;
1643 }
1644
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001645 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001646 if (r == SSH_EOF) {
1647 sprintf(msg, "SSH channel unexpected EOF");
1648 session->status = NC_STATUS_INVALID;
1649 session->term_reason = NC_SESSION_TERM_DROPPED;
1650 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1651 } else if (r == SSH_ERROR) {
1652 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001653 session->status = NC_STATUS_INVALID;
1654 session->term_reason = NC_SESSION_TERM_OTHER;
1655 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001656 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001657 /* no application data received */
1658 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001659 } else {
1660 /* we have some application data */
1661 ret = NC_PSPOLL_RPC;
1662 }
1663 break;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001664 case NC_TI_OPENSSL:
1665 r = SSL_pending(session->ti.tls);
1666 if (!r) {
1667 /* no data pending in the SSL buffer, poll fd */
1668 pfd.fd = SSL_get_rfd(session->ti.tls);
1669 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001670 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001671 ret = NC_PSPOLL_ERROR;
1672 break;
1673 }
1674 pfd.events = POLLIN;
1675 pfd.revents = 0;
1676 r = poll(&pfd, 1, 0);
1677
1678 if ((r < 0) && (errno != EINTR)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001679 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001680 session->status = NC_STATUS_INVALID;
1681 ret = NC_PSPOLL_ERROR;
1682 } else if (r > 0) {
1683 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001684 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001685 session->status = NC_STATUS_INVALID;
1686 session->term_reason = NC_SESSION_TERM_DROPPED;
1687 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1688 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001689 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001690 session->status = NC_STATUS_INVALID;
1691 session->term_reason = NC_SESSION_TERM_OTHER;
1692 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1693 } else {
1694 ret = NC_PSPOLL_RPC;
1695 }
1696 } else {
1697 ret = NC_PSPOLL_TIMEOUT;
1698 }
1699 } else {
1700 ret = NC_PSPOLL_RPC;
1701 }
1702 break;
romanf578cd52023-10-19 09:47:40 +02001703#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001704 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001705 case NC_TI_UNIX:
1706 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001707 pfd.events = POLLIN;
1708 pfd.revents = 0;
1709 r = poll(&pfd, 1, 0);
1710
1711 if ((r < 0) && (errno != EINTR)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001712 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001713 session->status = NC_STATUS_INVALID;
1714 ret = NC_PSPOLL_ERROR;
1715 } else if (r > 0) {
1716 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001717 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001718 session->status = NC_STATUS_INVALID;
1719 session->term_reason = NC_SESSION_TERM_DROPPED;
1720 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1721 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001722 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001723 session->status = NC_STATUS_INVALID;
1724 session->term_reason = NC_SESSION_TERM_OTHER;
1725 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1726 } else {
1727 ret = NC_PSPOLL_RPC;
1728 }
1729 } else {
1730 ret = NC_PSPOLL_TIMEOUT;
1731 }
1732 break;
1733 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001734 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001735 ret = NC_PSPOLL_ERROR;
1736 break;
1737 }
1738
Michal Vasko131120a2018-05-29 15:44:02 +02001739 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001740 return ret;
1741}
1742
1743API int
1744nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1745{
Michal Vasko443faa02022-10-20 09:09:03 +02001746 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001747 uint8_t q_id;
1748 uint16_t i, j;
1749 char msg[256];
1750 struct timespec ts_timeout, ts_cur;
1751 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001752 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001753 struct nc_server_rpc *rpc = NULL;
1754
romanf578cd52023-10-19 09:47:40 +02001755 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001756
Michal Vaskoade892d2017-02-22 13:40:35 +01001757 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001758 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001759 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001760 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001761
Michal Vaskoade892d2017-02-22 13:40:35 +01001762 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001763 nc_ps_unlock(ps, q_id, __func__);
1764 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001765 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001766
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001767 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001768 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001769 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001770 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001771 }
1772
1773 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001774 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001775 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001776 if (ps->last_event_session == ps->session_count - 1) {
1777 i = j = 0;
1778 } else {
1779 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001780 }
Michal Vasko9a327362017-01-11 11:31:46 +01001781 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001782 cur_ps_session = ps->sessions[i];
1783 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001784
Michal Vasko131120a2018-05-29 15:44:02 +02001785 /* SESSION RPC LOCK */
1786 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001787 if (r == -1) {
1788 ret = NC_PSPOLL_ERROR;
1789 } else if (r == 1) {
1790 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001791 switch (cur_ps_session->state) {
1792 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001793 if (cur_session->status == NC_STATUS_RUNNING) {
1794 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001795 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001796
Michal Vasko131120a2018-05-29 15:44:02 +02001797 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001798 switch (ret) {
1799 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001800 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001801 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001802 break;
1803 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001804 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001805 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001806 break;
1807 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001808#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001809 case NC_PSPOLL_SSH_CHANNEL:
1810 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001811#endif /* NC_ENABLED_SSH_TLS */
fanchanghu3d4e7212017-08-09 09:42:30 +08001812 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001813 break;
1814 case NC_PSPOLL_RPC:
1815 /* let's keep the state busy, we are not done with this session */
1816 break;
1817 }
1818 } else {
1819 /* session is not fine, let the caller know */
1820 ret = NC_PSPOLL_SESSION_TERM;
1821 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1822 ret |= NC_PSPOLL_SESSION_ERROR;
1823 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001824 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001825 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001826 break;
1827 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001828 /* it definitely should not be busy because we have the lock */
1829 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001830 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001831 break;
1832 case NC_PS_STATE_INVALID:
1833 /* we got it locked, but it will be freed, let it be */
1834 ret = NC_PSPOLL_TIMEOUT;
1835 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001836 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001837
Michal Vasko131120a2018-05-29 15:44:02 +02001838 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001839 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001840 /* SESSION RPC UNLOCK */
1841 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001842 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001843 } else {
1844 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001845 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001846 }
Michal Vasko428087d2016-01-14 16:04:28 +01001847
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001848 /* something happened */
1849 if (ret != NC_PSPOLL_TIMEOUT) {
1850 break;
1851 }
1852
Michal Vasko9a327362017-01-11 11:31:46 +01001853 if (i == ps->session_count - 1) {
1854 i = 0;
1855 } else {
1856 ++i;
1857 }
1858 } while (i != j);
1859
Michal Vaskoade892d2017-02-22 13:40:35 +01001860 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001861 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001862 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001863
Michal Vaskod8a74192023-02-06 15:51:50 +01001864 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001865 /* final timeout */
1866 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001867 }
Michal Vasko428087d2016-01-14 16:04:28 +01001868 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001869 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001870
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001871 /* do we want to return the session? */
1872 switch (ret) {
1873 case NC_PSPOLL_RPC:
1874 case NC_PSPOLL_SESSION_TERM:
1875 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001876#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001877 case NC_PSPOLL_SSH_CHANNEL:
1878 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001879#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001880 if (session) {
1881 *session = cur_session;
1882 }
1883 ps->last_event_session = i;
1884 break;
1885 default:
1886 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001887 }
Michal Vasko428087d2016-01-14 16:04:28 +01001888
Michal Vaskoade892d2017-02-22 13:40:35 +01001889 /* PS UNLOCK */
1890 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001891
Michal Vasko131120a2018-05-29 15:44:02 +02001892 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001893 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001894 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001895 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1896 if (cur_session->status != NC_STATUS_RUNNING) {
1897 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001898 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001899 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001900 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001901 }
1902 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001903 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001904
Michal Vasko7f1ee932018-10-11 09:41:42 +02001905 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001906 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001907 if (cur_session->status != NC_STATUS_RUNNING) {
1908 ret |= NC_PSPOLL_SESSION_TERM;
1909 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1910 ret |= NC_PSPOLL_SESSION_ERROR;
1911 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001912 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001913 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001914 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001915 }
Michal Vasko428087d2016-01-14 16:04:28 +01001916 }
Michal Vasko77367452021-02-16 16:32:18 +01001917 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001918
Michal Vasko131120a2018-05-29 15:44:02 +02001919 /* SESSION RPC UNLOCK */
1920 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001921 }
1922
Michal Vasko48a63ed2016-03-01 09:48:21 +01001923 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001924}
1925
Michal Vaskod09eae62016-02-01 10:32:52 +01001926API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001927nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001928{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001929 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001930 uint16_t i;
1931 struct nc_session *session;
1932
Michal Vasko9a25e932016-02-01 10:36:42 +01001933 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001934 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001935 return;
1936 }
1937
Michal Vasko48a63ed2016-03-01 09:48:21 +01001938 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001939 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001940 return;
1941 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001942
Michal Vasko48a63ed2016-03-01 09:48:21 +01001943 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001944 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001945 nc_session_free(ps->sessions[i]->session, data_free);
1946 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001947 }
1948 free(ps->sessions);
1949 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001950 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001951 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001952 } else {
1953 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001954 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1955 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001956 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001957 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001958 continue;
1959 }
1960
1961 ++i;
1962 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001963 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001964
1965 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001966 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001967}
1968
romanfb3f7cf2023-11-30 16:10:09 +01001969int
1970nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1971{
1972 int sock = -1, set_addr, ret = 0;
1973
1974 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1975
1976 if (address) {
1977 set_addr = 1;
1978 } else {
1979 set_addr = 0;
1980 }
1981
1982 if (set_addr) {
1983 port = bind->port;
1984 } else {
1985 address = bind->address;
1986 }
1987
1988 /* we have all the information we need to create a listening socket */
1989 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1990 /* create new socket, close the old one */
1991 if (endpt->ti == NC_TI_UNIX) {
1992 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1993 } else {
1994 sock = nc_sock_listen_inet(address, port, &endpt->ka);
1995 }
1996
1997 if (sock == -1) {
1998 ret = 1;
1999 goto cleanup;
2000 }
2001
2002 if (bind->sock > -1) {
2003 close(bind->sock);
2004 }
2005 bind->sock = sock;
2006 }
2007
2008 if (sock > -1) {
2009 switch (endpt->ti) {
2010 case NC_TI_UNIX:
2011 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2012 break;
2013#ifdef NC_ENABLED_SSH_TLS
2014 case NC_TI_LIBSSH:
2015 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2016 break;
2017 case NC_TI_OPENSSL:
2018 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2019 break;
2020#endif /* NC_ENABLED_SSH_TLS */
2021 default:
2022 ERRINT;
2023 ret = 1;
2024 break;
2025 }
2026 }
2027
2028cleanup:
2029 return ret;
2030}
2031
Michal Vasko6f865982023-11-21 12:10:42 +01002032/**
2033 * @brief Get UID of the owner of a socket.
2034 *
2035 * @param[in] sock Socket to analyze.
2036 * @param[out] uid Socket owner UID.
2037 * @return 0 on success,
2038 * @return -1 on error.
2039 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002040static int
apropp-molex4e903c32020-04-20 03:06:58 -04002041nc_get_uid(int sock, uid_t *uid)
2042{
Michal Vasko6f865982023-11-21 12:10:42 +01002043 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002044
Michal Vaskod3910912020-04-20 09:12:49 +02002045#ifdef SO_PEERCRED
2046 struct ucred ucred;
2047 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002048
Michal Vaskod3910912020-04-20 09:12:49 +02002049 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002050 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2051 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002052 *uid = ucred.uid;
2053 }
2054#else
Michal Vasko6f865982023-11-21 12:10:42 +01002055 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002056#endif
2057
Michal Vasko6f865982023-11-21 12:10:42 +01002058 if (r < 0) {
2059 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002060 return -1;
2061 }
apropp-molex4e903c32020-04-20 03:06:58 -04002062 return 0;
2063}
2064
2065static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002066nc_accept_unix(struct nc_session *session, int sock)
2067{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002068#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002069 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002070 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002071
Michal Vasko5f352c52019-07-10 16:12:06 +02002072 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002073 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002074 char *buf = NULL;
2075 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002076
Michal Vaskod3910912020-04-20 09:12:49 +02002077 if (nc_get_uid(sock, &uid)) {
2078 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002079 return -1;
2080 }
2081
romanf6e32012023-04-24 15:51:26 +02002082 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002083 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002084 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002085 close(sock);
2086 return -1;
2087 }
2088
2089 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002090 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002091 if (username == NULL) {
2092 ERRMEM;
2093 close(sock);
2094 return -1;
2095 }
Michal Vasko93224072021-11-09 12:14:28 +01002096 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002097
2098 session->ti.unixsock.sock = sock;
2099
2100 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002101#else
2102 return -1;
2103#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002104}
2105
Michal Vaskoe2713da2016-08-22 16:06:40 +02002106API int
romanfb3f7cf2023-11-30 16:10:09 +01002107nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2108{
2109 int ret = 0;
2110 void *tmp;
2111 uint16_t i;
2112
2113 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2114
2115 /* CONFIG LOCK */
2116 pthread_rwlock_wrlock(&server_opts.config_lock);
2117
2118 /* check name uniqueness */
2119 for (i = 0; i < server_opts.endpt_count; i++) {
2120 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2121 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2122 ret = 1;
2123 goto cleanup;
2124 }
2125 }
2126
2127 /* alloc a new endpoint */
2128 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2129 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2130 server_opts.endpts = tmp;
2131 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2132
2133 /* alloc a new bind */
2134 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2135 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2136 server_opts.binds = tmp;
2137 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2138 server_opts.binds[server_opts.endpt_count].sock = -1;
2139 server_opts.endpt_count++;
2140
2141 /* set name and ti */
2142 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2143 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2144 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2145
2146 /* set the bind data */
2147 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2148 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2149
2150 /* alloc unix opts */
2151 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2152 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2153
2154 /* set the opts data */
2155 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2156 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2157 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2158 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2159 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2160
2161 /* start listening */
2162 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2163 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2164 if (ret) {
2165 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2166 goto cleanup;
2167 }
2168
2169cleanup:
2170 /* CONFIG UNLOCK */
2171 pthread_rwlock_unlock(&server_opts.config_lock);
2172 return ret;
2173}
2174
2175static void
2176nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2177{
2178 if (bind->sock > -1) {
2179 close(bind->sock);
2180 }
2181
2182 unlink(bind->address);
2183 free(bind->address);
2184 free(opts->address);
2185
2186 free(opts);
2187}
2188
2189void
2190_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2191{
2192 free(endpt->name);
2193 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2194
2195 server_opts.endpt_count--;
2196 if (!server_opts.endpt_count) {
2197 free(server_opts.endpts);
2198 free(server_opts.binds);
2199 server_opts.endpts = NULL;
2200 server_opts.binds = NULL;
2201 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2202 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2203 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2204 }
2205}
2206
2207API void
2208nc_server_del_endpt_unix_socket(const char *endpt_name)
2209{
2210 uint16_t i;
2211 struct nc_endpt *endpt = NULL;
2212 struct nc_bind *bind;
2213
romanb6ad37a2023-12-07 13:08:46 +01002214 NC_CHECK_ARG_RET(NULL, endpt_name, );
2215
romanfb3f7cf2023-11-30 16:10:09 +01002216 /* CONFIG LOCK */
2217 pthread_rwlock_wrlock(&server_opts.config_lock);
2218
romanfb3f7cf2023-11-30 16:10:09 +01002219 for (i = 0; i < server_opts.endpt_count; i++) {
2220 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2221 endpt = &server_opts.endpts[i];
2222 bind = &server_opts.binds[i];
2223 break;
2224 }
2225 }
2226 if (!endpt) {
2227 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2228 goto end;
2229 }
2230 if (endpt->ti != NC_TI_UNIX) {
2231 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2232 goto end;
2233 }
2234
2235 _nc_server_del_endpt_unix_socket(endpt, bind);
2236
2237end:
2238 /* CONFIG UNLOCK */
2239 pthread_rwlock_unlock(&server_opts.config_lock);
2240}
2241
2242API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002243nc_server_endpt_count(void)
2244{
2245 return server_opts.endpt_count;
2246}
2247
Michal Vasko71090fc2016-05-24 16:37:28 +02002248API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002249nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002250{
Michal Vasko71090fc2016-05-24 16:37:28 +02002251 NC_MSG_TYPE msgtype;
Michal Vasko1a38c862016-01-15 15:50:07 +01002252 int sock, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002253 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002254 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002255 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002256
romanf578cd52023-10-19 09:47:40 +02002257 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002258
Michal Vasko93224072021-11-09 12:14:28 +01002259 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002260 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002261
romanf578cd52023-10-19 09:47:40 +02002262 /* CONFIG LOCK */
2263 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002264
2265 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002266 ERR(NULL, "No endpoints to accept sessions on.");
romanf578cd52023-10-19 09:47:40 +02002267 /* CONFIG UNLOCK */
2268 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002269 return NC_MSG_ERROR;
Michal Vasko51e514d2016-02-02 15:51:52 +01002270 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002271
romanf578cd52023-10-19 09:47:40 +02002272 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 +01002273 if (ret < 1) {
Michal Vaskob737d752016-02-09 09:01:27 +01002274 free(host);
romanf578cd52023-10-19 09:47:40 +02002275 /* CONFIG UNLOCK */
2276 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko5e203472016-05-30 15:27:58 +02002277 if (!ret) {
2278 return NC_MSG_WOULDBLOCK;
2279 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002280 return NC_MSG_ERROR;
Michal Vasko9e036d52016-01-08 10:49:26 +01002281 }
Michal Vaskoade892d2017-02-22 13:40:35 +01002282
Michal Vaskob48aa812016-01-18 14:13:09 +01002283 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002284
Michal Vasko131120a2018-05-29 15:44:02 +02002285 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002286 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(host); msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002287 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002288 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002289 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002290 (*session)->host = host;
Michal Vasko1a38c862016-01-15 15:50:07 +01002291 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002292
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002293 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002294#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002295 if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002296 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal 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 Vasko71090fc2016-05-24 16:37:28 +02002307 if (ret < 0) {
2308 msgtype = NC_MSG_ERROR;
2309 goto cleanup;
2310 } else if (!ret) {
2311 msgtype = NC_MSG_WOULDBLOCK;
2312 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002313 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002314 } else
romanf578cd52023-10-19 09:47:40 +02002315#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002316 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2317 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2318 ret = nc_accept_unix(*session, sock);
2319 if (ret < 0) {
2320 msgtype = NC_MSG_ERROR;
2321 goto cleanup;
2322 }
2323 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002324 ERRINT;
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002325 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002326 msgtype = NC_MSG_ERROR;
2327 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002328 }
2329
Michal Vasko2cc4c682016-03-01 09:16:48 +01002330 (*session)->data = NULL;
2331
romanf578cd52023-10-19 09:47:40 +02002332 /* CONFIG UNLOCK */
2333 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002334
Michal Vaskob48aa812016-01-18 14:13:09 +01002335 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002336 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002337
Michal Vasko9e036d52016-01-08 10:49:26 +01002338 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002339 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002340 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002341 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002342 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002343 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002344 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002345
Michal Vaskod8a74192023-02-06 15:51:50 +01002346 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002347 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002348 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002349 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002350 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002351
Michal Vasko71090fc2016-05-24 16:37:28 +02002352 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002353
Michal Vasko71090fc2016-05-24 16:37:28 +02002354cleanup:
romanf578cd52023-10-19 09:47:40 +02002355 /* CONFIG UNLOCK */
2356 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002357
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002358 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002359 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002360 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002361}
2362
romanf578cd52023-10-19 09:47:40 +02002363#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002364
2365API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002366nc_server_ch_is_client(const char *name)
2367{
2368 uint16_t i;
2369 int found = 0;
2370
2371 if (!name) {
2372 return found;
2373 }
2374
2375 /* READ LOCK */
2376 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2377
2378 /* check name uniqueness */
2379 for (i = 0; i < server_opts.ch_client_count; ++i) {
2380 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2381 found = 1;
2382 break;
2383 }
2384 }
2385
2386 /* UNLOCK */
2387 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2388
2389 return found;
2390}
2391
2392API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002393nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2394{
2395 uint16_t i;
2396 struct nc_ch_client *client = NULL;
2397 int found = 0;
2398
2399 if (!client_name || !endpt_name) {
2400 return found;
2401 }
2402
2403 /* READ LOCK */
2404 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2405
2406 for (i = 0; i < server_opts.ch_client_count; ++i) {
2407 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2408 client = &server_opts.ch_clients[i];
2409 break;
2410 }
2411 }
2412
2413 if (!client) {
2414 goto cleanup;
2415 }
2416
2417 for (i = 0; i < client->ch_endpt_count; ++i) {
2418 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2419 found = 1;
2420 break;
2421 }
2422 }
2423
2424cleanup:
2425 /* UNLOCK */
2426 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2427 return found;
2428}
2429
Michal Vasko056f53c2022-10-21 13:38:15 +02002430/**
2431 * @brief Create a connection for an endpoint.
2432 *
2433 * Client lock is expected to be held.
2434 *
2435 * @param[in] endpt Endpoint to use.
2436 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2437 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2438 * @param[in] ctx_cb_data Context callbacks data.
2439 * @param[out] session Created NC session.
2440 * @return NC_MSG values.
2441 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002442static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002443nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2444 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 +01002445{
Michal Vasko71090fc2016-05-24 16:37:28 +02002446 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002447 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002448 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002449 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002450 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002451
Michal Vasko056f53c2022-10-21 13:38:15 +02002452 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 +01002453 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002454 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002455 }
2456
Michal Vasko93224072021-11-09 12:14:28 +01002457 /* acquire context */
2458 ctx = acquire_ctx_cb(ctx_cb_data);
2459 if (!ctx) {
2460 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2461 close(sock);
2462 free(ip_host);
2463 return NC_MSG_ERROR;
2464 }
2465
romanf578cd52023-10-19 09:47:40 +02002466 /* init ctx as needed */
2467 nc_server_init_cb_ctx(ctx);
2468
Michal Vasko93224072021-11-09 12:14:28 +01002469 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002470 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002471 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002472 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002473 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002474 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002475 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002476 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002477
Michal Vaskob05053d2016-01-22 16:12:06 +01002478 /* sock gets assigned to session or closed */
Michal Vaskoadf30f02019-06-24 09:34:47 +02002479 if (endpt->ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002480 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002481 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002482
Michal Vasko71090fc2016-05-24 16:37:28 +02002483 if (ret < 0) {
2484 msgtype = NC_MSG_ERROR;
2485 goto fail;
2486 } else if (!ret) {
2487 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002488 goto fail;
2489 }
romanf578cd52023-10-19 09:47:40 +02002490 } else if (endpt->ti == NC_TI_OPENSSL) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002491 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002492 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002493 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002494
Michal Vasko71090fc2016-05-24 16:37:28 +02002495 if (ret < 0) {
2496 msgtype = NC_MSG_ERROR;
2497 goto fail;
2498 } else if (!ret) {
2499 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002500 goto fail;
2501 }
roman423cc0d2023-11-24 11:29:47 +01002502 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002503 ERRINT;
2504 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002505 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002506 goto fail;
2507 }
2508
2509 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002510 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002511
2512 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002513 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002514 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002515 goto fail;
2516 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002517
Michal Vaskod8a74192023-02-06 15:51:50 +01002518 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002519 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002520 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002521 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002522 (*session)->status = NC_STATUS_RUNNING;
2523
Michal Vasko71090fc2016-05-24 16:37:28 +02002524 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002525
2526fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002527 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002528 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002529 if (ctx) {
2530 release_ctx_cb(ctx_cb_data);
2531 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002532 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002533}
2534
Michal Vasko6f865982023-11-21 12:10:42 +01002535/**
2536 * @brief Wait for any event after a NC session was established on a CH client.
2537 *
Michal Vasko6f865982023-11-21 12:10:42 +01002538 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002539 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002540 * @return 0 if session was terminated normally,
2541 * @return 1 if the CH client was removed,
2542 * @return -1 on error.
2543 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002544static int
roman8341e8b2023-11-23 16:12:42 +01002545nc_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 +02002546{
Michal Vasko6f865982023-11-21 12:10:42 +01002547 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002548 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002549 struct timespec ts;
2550 struct nc_ch_client *client;
2551
Michal Vasko2e6defd2016-10-07 15:48:15 +02002552 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002553 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002554
Michal Vaskofeccb312022-03-24 15:24:59 +01002555 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002556
Michal Vasko2e6defd2016-10-07 15:48:15 +02002557 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002558 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002559 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002560 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002561
2562 /* CH UNLOCK */
2563 pthread_mutex_unlock(&session->opts.server.ch_lock);
2564
Michal Vasko77d56d72022-09-07 10:30:48 +02002565 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002566 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002567 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002568 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002569 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002570
2571 do {
romanf578cd52023-10-19 09:47:40 +02002572 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002573
Michal Vasko0db3db52021-03-03 10:45:42 +01002574 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002575 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 +01002576 if (!r) {
2577 /* we were woken up, something probably happened */
2578 if (session->status != NC_STATUS_RUNNING) {
2579 break;
2580 }
2581 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002582 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002583 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002584 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002585 }
2586
Michal Vasko2e6defd2016-10-07 15:48:15 +02002587 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002588
Michal Vasko2e6defd2016-10-07 15:48:15 +02002589 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002590 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002591 if (!client) {
2592 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002593 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002594 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002595 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002596 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002597 }
2598
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002599 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002600 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002601 } else {
2602 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002603 }
2604
Michal Vaskod8a74192023-02-06 15:51:50 +01002605 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002606 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 +02002607 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002608 session->status = NC_STATUS_INVALID;
2609 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2610 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002611
2612 /* UNLOCK */
2613 nc_server_ch_client_unlock(client);
2614
2615 } while (session->status == NC_STATUS_RUNNING);
2616
Michal Vaskofeccb312022-03-24 15:24:59 +01002617 /* signal to nc_session_free() that CH thread is terminating */
2618 session->flags &= ~NC_SESSION_CH_THREAD;
2619 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002620
Michal Vasko27377422018-03-15 08:59:35 +01002621 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002622 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002623
Michal Vasko6f865982023-11-21 12:10:42 +01002624 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002625}
2626
romanf578cd52023-10-19 09:47:40 +02002627/**
2628 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2629 *
2630 * @param[in] session An established session.
2631 * @param[in] data Call Home thread's data.
2632 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2633 *
2634 * @return 0 if the thread should stop running, 1 if it should continue.
2635 */
2636static int
2637nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2638{
2639 struct timespec ts;
2640 int ret = 0, thread_running;
2641
2642 /* COND LOCK */
2643 pthread_mutex_lock(&data->cond_lock);
2644 /* get reconnect timeout in ms */
2645 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2646 while (!ret && data->thread_running) {
2647 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2648 }
2649
2650 thread_running = data->thread_running;
2651 /* COND UNLOCK */
2652 pthread_mutex_unlock(&data->cond_lock);
2653
2654 if (!thread_running) {
2655 /* thread is terminating */
2656 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2657 ret = 0;
2658 } else if (ret == ETIMEDOUT) {
2659 /* time to reconnect */
2660 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2661 ret = 1;
2662 } else if (ret) {
2663 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2664 ret = 0;
2665 }
2666
2667 return ret;
2668}
2669
2670/**
2671 * @brief Checks if a Call Home thread should terminate.
2672 *
2673 * Checks the shared boolean variable thread_running. This should be done everytime
2674 * before entering a critical section.
2675 *
2676 * @param[in] data Call Home thread's data.
2677 *
2678 * @return 0 if the thread should stop running, -1 if it can continue.
2679 */
2680static int
2681nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2682{
2683 int ret = -1;
2684
2685 /* COND LOCK */
2686 pthread_mutex_lock(&data->cond_lock);
2687 if (!data->thread_running) {
2688 /* thread should stop running */
2689 ret = 0;
2690 }
2691 /* COND UNLOCK */
2692 pthread_mutex_unlock(&data->cond_lock);
2693
2694 return ret;
2695}
2696
Michal Vasko6f865982023-11-21 12:10:42 +01002697/**
2698 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2699 *
2700 * @param[in] name Name of the CH client.
2701 * @return Pointer to the CH client.
2702 */
2703static struct nc_ch_client *
2704nc_server_ch_client_with_endpt_lock(const char *name)
2705{
2706 struct nc_ch_client *client;
2707
2708 while (1) {
2709 /* LOCK */
2710 client = nc_server_ch_client_lock(name);
2711 if (!client) {
2712 return NULL;
2713 }
2714 if (client->ch_endpt_count) {
2715 return client;
2716 }
2717 /* no endpoints defined yet */
2718
2719 /* UNLOCK */
2720 nc_server_ch_client_unlock(client);
2721
2722 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2723 }
2724
2725 return NULL;
2726}
2727
2728/**
2729 * @brief Call Home client management thread.
2730 *
2731 * @param[in] arg CH client thread argument.
2732 * @return NULL.
2733 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002734static void *
2735nc_ch_client_thread(void *arg)
2736{
Michal Vasko6f865982023-11-21 12:10:42 +01002737 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002738 NC_MSG_TYPE msgtype;
2739 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002740 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002741 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002742 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002743 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002744 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002745 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002746
2747 /* LOCK */
2748 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002749 if (!client) {
2750 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2751 goto cleanup;
2752 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002753
2754 cur_endpt = &client->ch_endpts[0];
2755 cur_endpt_name = strdup(cur_endpt->name);
2756
Michal Vasko6f865982023-11-21 12:10:42 +01002757 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002758 if (!cur_attempts) {
2759 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2760 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002761
Michal Vasko6f865982023-11-21 12:10:42 +01002762 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 +02002763 if (msgtype == NC_MSG_HELLO) {
2764 /* UNLOCK */
2765 nc_server_ch_client_unlock(client);
2766
romanf578cd52023-10-19 09:47:40 +02002767 if (!nc_server_ch_client_thread_is_running(data)) {
2768 /* thread should stop running */
2769 goto cleanup;
2770 }
2771
2772 /* run while the session is established */
2773 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002774 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002775 goto cleanup;
2776 }
roman8341e8b2023-11-23 16:12:42 +01002777 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002778
roman8341e8b2023-11-23 16:12:42 +01002779 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002780 if (!nc_server_ch_client_thread_is_running(data)) {
2781 /* thread should stop running */
2782 goto cleanup;
2783 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002784
2785 /* LOCK */
2786 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002787 if (!client) {
2788 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2789 goto cleanup;
2790 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002791
2792 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002793 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002794 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002795 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002796 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002797 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002798 } else {
2799 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002800 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002801 }
2802
Michal Vasko2e6defd2016-10-07 15:48:15 +02002803 /* UNLOCK */
2804 nc_server_ch_client_unlock(client);
2805
romanf578cd52023-10-19 09:47:40 +02002806 /* wait for the timeout to elapse, so we can try to reconnect */
2807 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2808 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2809 goto cleanup;
2810 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002811
2812 /* LOCK */
2813 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002814 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002815 }
2816
2817 /* set next endpoint to try */
2818 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002819 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002820 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002821 /* we keep the current one but due to unlock/lock we have to find it again */
2822 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2823 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2824 break;
2825 }
2826 }
2827 if (next_endpt_index >= client->ch_endpt_count) {
2828 /* endpoint was removed, start with the first one */
2829 next_endpt_index = 0;
2830 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002831 } else {
2832 /* just get a random index */
2833 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002834 }
2835
Michal Vasko2e6defd2016-10-07 15:48:15 +02002836 } else {
romanf578cd52023-10-19 09:47:40 +02002837 /* session was not created, wait a little bit and try again */
2838 max_wait = client->max_wait;
2839
Michal Vasko6bb116b2016-10-26 13:53:46 +02002840 /* UNLOCK */
2841 nc_server_ch_client_unlock(client);
2842
romanf578cd52023-10-19 09:47:40 +02002843 /* wait for max_wait seconds */
2844 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2845 /* thread should stop running */
2846 goto cleanup;
2847 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002848
Michal Vasko6bb116b2016-10-26 13:53:46 +02002849 /* LOCK */
2850 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002851 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002852
Michal Vasko2e6defd2016-10-07 15:48:15 +02002853 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002854
2855 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002856 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2857 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002858 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002859 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002860 }
2861
Peter Feiged05f2252018-09-03 08:09:47 +00002862 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002863 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002864 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002865 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002866 cur_attempts = 0;
2867 } else if (cur_attempts == client->max_attempts) {
2868 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002869 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002870 data->client_name, cur_endpt_name, client->max_attempts);
2871
2872 /* clear a pending socket, if any */
2873 cur_endpt = &client->ch_endpts[next_endpt_index];
2874 if (cur_endpt->sock_pending > -1) {
2875 close(cur_endpt->sock_pending);
2876 cur_endpt->sock_pending = -1;
2877 }
2878
Peter Feiged05f2252018-09-03 08:09:47 +00002879 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002880 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002881 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002882 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002883 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002884 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002885 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002886 cur_attempts = 0;
2887 } /* else we keep the current one */
2888 }
Peter Feiged05f2252018-09-03 08:09:47 +00002889
2890 cur_endpt = &client->ch_endpts[next_endpt_index];
2891 free(cur_endpt_name);
2892 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002893 }
Michal Vasko6f865982023-11-21 12:10:42 +01002894
romanf578cd52023-10-19 09:47:40 +02002895 /* UNLOCK if we break out of the loop */
2896 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002897
2898cleanup:
romanf578cd52023-10-19 09:47:40 +02002899 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002900 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002901 free(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002902 pthread_cond_destroy(&data->cond);
2903 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002904 free(data);
2905 return NULL;
2906}
2907
2908API int
Michal Vasko93224072021-11-09 12:14:28 +01002909nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002910 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2911 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002912{
Michal Vasko6f865982023-11-21 12:10:42 +01002913 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002914 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002915 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002916 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002917
romanf578cd52023-10-19 09:47:40 +02002918 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2919
Michal Vasko6f865982023-11-21 12:10:42 +01002920 /* LOCK */
2921 ch_client = nc_server_ch_client_lock(client_name);
2922 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002923 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002924 return -1;
2925 }
2926
Michal Vasko6f865982023-11-21 12:10:42 +01002927 /* create the thread argument */
2928 arg = calloc(1, sizeof *arg);
2929 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002930 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002931 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002932 arg->acquire_ctx_cb = acquire_ctx_cb;
2933 arg->release_ctx_cb = release_ctx_cb;
2934 arg->ctx_cb_data = ctx_cb_data;
2935 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002936 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002937 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002938 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002939
Michal Vasko6f865982023-11-21 12:10:42 +01002940 /* creating the thread */
2941 arg->thread_running = 1;
2942 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2943 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2944 rc = -1;
2945 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002946 }
Michal Vasko6f865982023-11-21 12:10:42 +01002947
Michal Vasko2e6defd2016-10-07 15:48:15 +02002948 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002949 ch_client->tid = tid;
2950 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002951 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002952
Michal Vasko6f865982023-11-21 12:10:42 +01002953cleanup:
2954 /* UNLOCK */
2955 nc_server_ch_client_unlock(ch_client);
2956
2957 if (arg) {
2958 free(arg->client_name);
2959 free(arg);
2960 }
2961 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002962}
2963
romanf578cd52023-10-19 09:47:40 +02002964#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002965
roman44efa322023-11-03 13:57:25 +01002966API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002967nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002968{
roman44efa322023-11-03 13:57:25 +01002969 struct timespec fail = {0};
2970
2971 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002972
2973 if (session->side != NC_SERVER) {
2974 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002975 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002976 }
2977
Michal Vasko2e6defd2016-10-07 15:48:15 +02002978 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002979}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002980
2981API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002982nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002983{
2984 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002985 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002986 return;
2987 }
2988
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002989 /* NTF STATUS LOCK */
2990 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
2991
Michal Vasko71dbd772021-03-23 14:08:37 +01002992 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002993
2994 /* NTF STATUS UNLOCK */
2995 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01002996}
2997
2998API void
2999nc_session_dec_notif_status(struct nc_session *session)
3000{
3001 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003002 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01003003 return;
3004 }
3005
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003006 /* NTF STATUS LOCK */
3007 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3008
Michal Vasko71dbd772021-03-23 14:08:37 +01003009 if (session->opts.server.ntf_status) {
3010 --session->opts.server.ntf_status;
3011 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003012
3013 /* NTF STATUS UNLOCK */
3014 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003015}
3016
3017API int
3018nc_session_get_notif_status(const struct nc_session *session)
3019{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003020 uint32_t ntf_status;
3021
Michal Vasko3486a7c2017-03-03 13:28:07 +01003022 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003023 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003024 return 0;
3025 }
3026
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003027 /* NTF STATUS LOCK */
3028 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3029
3030 ntf_status = session->opts.server.ntf_status;
3031
3032 /* NTF STATUS UNLOCK */
3033 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3034
3035 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003036}