blob: ff515ab5c015dd4027ccf9a8dbe5446a0f260e08 [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{
Michal Vasko1440a742021-03-31 11:11:03 +0200845 uint32_t i;
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
860 pthread_mutex_destroy(&server_opts.bind_lock);
861
862#ifdef NC_ENABLED_SSH_TLS
romana9ec3362023-12-21 10:59:57 +0100863 free(server_opts.authkey_path_fmt);
864 server_opts.authkey_path_fmt = NULL;
roman808f3f62023-11-23 16:01:04 +0100865 free(server_opts.pam_config_name);
866 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200867 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
868 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
869 }
870 server_opts.interactive_auth_data = NULL;
871 server_opts.interactive_auth_data_free = NULL;
872
romanf578cd52023-10-19 09:47:40 +0200873 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
874 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
875 curl_global_cleanup();
876#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100877}
878
Michal Vasko086311b2016-01-08 09:53:11 +0100879API int
880nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
881{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200882 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200883 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200884 return -1;
885 } 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 +0200886 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100887 return -1;
888 }
889
romanf578cd52023-10-19 09:47:40 +0200890 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
891 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100892 return 0;
893}
894
Michal Vasko1a38c862016-01-15 15:50:07 +0100895API void
Michal Vasko55f03972016-04-13 08:56:01 +0200896nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
897{
898 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200899 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200900 return;
901 }
902
903 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200904 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200905 }
906 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200907 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200908 }
909}
910
Michal Vasko55f03972016-04-13 08:56:01 +0200911API int
Radek Krejci658782b2016-12-04 22:04:55 +0100912nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200913{
Michal Vasko93224072021-11-09 12:14:28 +0100914 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100915
916 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200917 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100918 return EXIT_FAILURE;
919 }
920
Michal Vasko93224072021-11-09 12:14:28 +0100921 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200922 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100923 server_opts.capabilities = mem;
924
925 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
926 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100927
928 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200929}
930
Michal Vasko1a38c862016-01-15 15:50:07 +0100931API void
Michal Vasko1440a742021-03-31 11:11:03 +0200932nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
933 void (*free_user_data)(void *user_data))
934{
935 server_opts.content_id_clb = content_id_clb;
936 server_opts.content_id_data = user_data;
937 server_opts.content_id_data_free = free_user_data;
938}
939
Michal Vasko71090fc2016-05-24 16:37:28 +0200940API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100941nc_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 +0100942{
Michal Vasko71090fc2016-05-24 16:37:28 +0200943 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200944 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200945
romanf578cd52023-10-19 09:47:40 +0200946 NC_CHECK_ARG_RET(NULL, ctx, username, session, NC_MSG_ERROR);
947
948 if (fdin < 0) {
949 ERRARG(NULL, "fdin");
Michal Vasko71090fc2016-05-24 16:37:28 +0200950 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +0200951 } else if (fdout < 0) {
romanf578cd52023-10-19 09:47:40 +0200952 ERRARG(NULL, "fdout");
Michal Vasko71090fc2016-05-24 16:37:28 +0200953 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +0100954 }
955
Michal Vasko93224072021-11-09 12:14:28 +0100956 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200957 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100958
Michal Vasko086311b2016-01-08 09:53:11 +0100959 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200960 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200961 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100962 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100963
Michal Vasko086311b2016-01-08 09:53:11 +0100964 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100965 (*session)->ti_type = NC_TI_FD;
966 (*session)->ti.fd.in = fdin;
967 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100968
Michal Vasko93224072021-11-09 12:14:28 +0100969 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100970 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100971 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100972
Michal Vaskob48aa812016-01-18 14:13:09 +0100973 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200974 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100975
Michal Vasko086311b2016-01-08 09:53:11 +0100976 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200977 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200978 if (msgtype != NC_MSG_HELLO) {
979 nc_session_free(*session, NULL);
980 *session = NULL;
981 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100982 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200983
Michal Vaskod8a74192023-02-06 15:51:50 +0100984 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200985 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100986 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100987 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200988
Michal Vasko1a38c862016-01-15 15:50:07 +0100989 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100990
Michal Vasko71090fc2016-05-24 16:37:28 +0200991 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100992}
Michal Vasko9e036d52016-01-08 10:49:26 +0100993
Michal Vaskob30b99c2016-07-26 11:35:43 +0200994static void
Michal Vasko74c345f2018-02-07 10:37:11 +0100995nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
996{
997 uint8_t q_last;
998
999 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
1000 ERRINT;
1001 return;
1002 }
1003
1004 /* get a unique queue value (by adding 1 to the last added value, if any) */
1005 if (ps->queue_len) {
1006 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1007 *id = ps->queue[q_last] + 1;
1008 } else {
1009 *id = 0;
1010 }
1011
1012 /* add the id into the queue */
1013 ++ps->queue_len;
1014 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1015 ps->queue[q_last] = *id;
1016}
1017
1018static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001019nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1020{
Michal Vasko74c345f2018-02-07 10:37:11 +01001021 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001022
1023 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001024 /* get the actual queue idx */
1025 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001026
1027 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001028 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001029 /* another equal value, simply cannot be */
1030 ERRINT;
1031 }
Michal Vaskod8340032018-02-12 14:41:00 +01001032 if (found == 2) {
1033 /* move the following values */
1034 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1035 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001036 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001037 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001038 if (i == 0) {
1039 found = 1;
1040 } else {
1041 /* this is not okay, our id is in the middle of the queue */
1042 found = 2;
1043 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001044 }
1045 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001046 if (!found) {
1047 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001048 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001049 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001050
Michal Vasko103fe632018-02-12 16:37:45 +01001051 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001052 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001053 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001054 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1055 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001056}
1057
Michal Vaskof04a52a2016-04-07 10:52:10 +02001058int
Michal Vasko26043172016-07-26 14:08:59 +02001059nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001060{
1061 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001062 struct timespec ts;
1063
Michal Vaskobe86fe32016-04-07 10:43:03 +02001064 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001065 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001066 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001067 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001068 return -1;
1069 }
1070
Michal Vasko74c345f2018-02-07 10:37:11 +01001071 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001072 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001073 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001074 pthread_mutex_unlock(&ps->lock);
1075 return -1;
1076 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001077
1078 /* add ourselves into the queue */
1079 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001080 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 +02001081 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001082
1083 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001084 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001085 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001086
Michal Vaskod8a74192023-02-06 15:51:50 +01001087 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001088 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301089 /**
1090 * This may happen when another thread releases the lock and broadcasts the condition
1091 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1092 * but when actually this thread was ready for condition.
1093 */
preetbhansali629dfc42018-12-17 16:04:40 +05301094 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301095 break;
1096 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001097
Michal Vasko05532772021-06-03 12:12:38 +02001098 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001099 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001100 nc_ps_queue_remove_id(ps, *id);
1101 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001102 return -1;
1103 }
1104 }
1105
Michal Vaskobe86fe32016-04-07 10:43:03 +02001106 /* UNLOCK */
1107 pthread_mutex_unlock(&ps->lock);
1108
1109 return 0;
1110}
1111
Michal Vaskof04a52a2016-04-07 10:52:10 +02001112int
Michal Vasko26043172016-07-26 14:08:59 +02001113nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001114{
1115 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001116
1117 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001118 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001119 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001120 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001121 ret = -1;
1122 }
1123
Michal Vaskob30b99c2016-07-26 11:35:43 +02001124 /* we must be the first, it was our turn after all, right? */
1125 if (ps->queue[ps->queue_begin] != id) {
1126 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001127 /* UNLOCK */
1128 if (!ret) {
1129 pthread_mutex_unlock(&ps->lock);
1130 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001131 return -1;
1132 }
1133
Michal Vaskobe86fe32016-04-07 10:43:03 +02001134 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001135 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001136 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 +02001137 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001138
1139 /* broadcast to all other threads that the queue moved */
1140 pthread_cond_broadcast(&ps->cond);
1141
Michal Vaskobe86fe32016-04-07 10:43:03 +02001142 /* UNLOCK */
1143 if (!ret) {
1144 pthread_mutex_unlock(&ps->lock);
1145 }
1146
1147 return ret;
1148}
1149
Michal Vasko428087d2016-01-14 16:04:28 +01001150API struct nc_pollsession *
1151nc_ps_new(void)
1152{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001153 struct nc_pollsession *ps;
1154
1155 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001156 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001157 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001158 pthread_mutex_init(&ps->lock, NULL);
1159
1160 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001161}
1162
1163API void
1164nc_ps_free(struct nc_pollsession *ps)
1165{
fanchanghu3d4e7212017-08-09 09:42:30 +08001166 uint16_t i;
1167
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001168 if (!ps) {
1169 return;
1170 }
1171
Michal Vaskobe86fe32016-04-07 10:43:03 +02001172 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001173 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001174 }
1175
fanchanghu3d4e7212017-08-09 09:42:30 +08001176 for (i = 0; i < ps->session_count; i++) {
1177 free(ps->sessions[i]);
1178 }
1179
Michal Vasko428087d2016-01-14 16:04:28 +01001180 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001181 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001182 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001183
Michal Vasko428087d2016-01-14 16:04:28 +01001184 free(ps);
1185}
1186
1187API int
1188nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1189{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001190 uint8_t q_id;
1191
romanf578cd52023-10-19 09:47:40 +02001192 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001193
Michal Vasko48a63ed2016-03-01 09:48:21 +01001194 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001195 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001196 return -1;
1197 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001198
Michal Vasko428087d2016-01-14 16:04:28 +01001199 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001200 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001201 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001202 ERRMEM;
1203 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001204 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001205 return -1;
1206 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001207 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1208 if (!ps->sessions[ps->session_count - 1]) {
1209 ERRMEM;
1210 --ps->session_count;
1211 /* UNLOCK */
1212 nc_ps_unlock(ps, q_id, __func__);
1213 return -1;
1214 }
1215 ps->sessions[ps->session_count - 1]->session = session;
1216 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001217
Michal Vasko48a63ed2016-03-01 09:48:21 +01001218 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001219 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001220}
1221
Michal Vasko48a63ed2016-03-01 09:48:21 +01001222static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001223_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001224{
1225 uint16_t i;
1226
Radek Krejcid5f978f2016-03-03 13:14:45 +01001227 if (index >= 0) {
1228 i = (uint16_t)index;
1229 goto remove;
1230 }
Michal Vasko428087d2016-01-14 16:04:28 +01001231 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001232 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001233remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001234 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001235 if (i <= ps->session_count) {
1236 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001237 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001238 }
1239 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001240 free(ps->sessions);
1241 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001242 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001243 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001244 return 0;
1245 }
1246 }
1247
Michal Vaskof0537d82016-01-29 14:42:38 +01001248 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001249}
1250
Michal Vasko48a63ed2016-03-01 09:48:21 +01001251API int
1252nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1253{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001254 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001255 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001256
romanf578cd52023-10-19 09:47:40 +02001257 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001258
1259 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001260 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001261 return -1;
1262 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001263
Radek Krejcid5f978f2016-03-03 13:14:45 +01001264 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001265
1266 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001267 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001268
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001269 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001270}
1271
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001272API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001273nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001274{
1275 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001276 struct nc_session *ret = NULL;
1277
romanf578cd52023-10-19 09:47:40 +02001278 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001279
1280 /* LOCK */
1281 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1282 return NULL;
1283 }
1284
Michal Vasko4871c9d2017-10-09 14:48:39 +02001285 if (idx < ps->session_count) {
1286 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001287 }
1288
1289 /* UNLOCK */
1290 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1291
1292 return ret;
1293}
1294
Michal Vasko3ec3b112022-07-21 12:32:33 +02001295API struct nc_session *
1296nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1297{
1298 uint8_t q_id;
1299 uint16_t i;
1300 struct nc_session *ret = NULL;
1301
romanf578cd52023-10-19 09:47:40 +02001302 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001303
1304 /* LOCK */
1305 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1306 return NULL;
1307 }
1308
1309 for (i = 0; i < ps->session_count; ++i) {
1310 if (match_cb(ps->sessions[i]->session, cb_data)) {
1311 ret = ps->sessions[i]->session;
1312 break;
1313 }
1314 }
1315
1316 /* UNLOCK */
1317 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1318
1319 return ret;
1320}
1321
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001322API uint16_t
1323nc_ps_session_count(struct nc_pollsession *ps)
1324{
Michal Vasko47003942019-03-14 12:25:23 +01001325 uint8_t q_id;
1326 uint16_t session_count;
1327
romanf578cd52023-10-19 09:47:40 +02001328 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001329
Michal Vasko47003942019-03-14 12:25:23 +01001330 /* LOCK (just for memory barrier so that we read the current value) */
1331 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1332 return 0;
1333 }
1334
1335 session_count = ps->session_count;
1336
1337 /* UNLOCK */
1338 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1339
1340 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001341}
1342
Michal Vasko77e83572022-07-21 15:31:15 +02001343static NC_MSG_TYPE
1344recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1345{
1346 struct lyd_attr *attr;
1347
1348 assert(envp && !envp->schema);
1349
1350 /* find the message-id attribute */
1351 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1352 if (!strcmp(attr->name.name, "message-id")) {
1353 break;
1354 }
1355 }
1356
1357 if (!attr) {
1358 ERR(session, "Received an <rpc> without a message-id.");
1359 return NC_MSG_REPLY_ERR_MSGID;
1360 }
1361
1362 return NC_MSG_RPC;
1363}
1364
Michal Vasko131120a2018-05-29 15:44:02 +02001365/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001366 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001367 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001368 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001369 * NC_PSPOLL_RPC
1370 */
1371static int
Michal Vasko131120a2018-05-29 15:44:02 +02001372nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001373{
Michal Vasko77367452021-02-16 16:32:18 +01001374 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001375 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001376 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001377 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001378
romanf578cd52023-10-19 09:47:40 +02001379 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1380
1381 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001382 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001383 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001384 }
1385
Michal Vasko93224072021-11-09 12:14:28 +01001386 *rpc = NULL;
1387
Michal Vasko77367452021-02-16 16:32:18 +01001388 /* get a message */
1389 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1390 if (r == -2) {
1391 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001392 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001393 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001394 }
1395 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001396 return NC_PSPOLL_ERROR;
1397 } else if (!r) {
1398 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001399 }
1400
Michal Vasko77367452021-02-16 16:32:18 +01001401 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001402 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001403
1404 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001405 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1406 /* check message-id */
1407 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1408 /* valid RPC */
1409 ret = NC_PSPOLL_RPC;
1410 } else {
1411 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001412 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1413 }
1414 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001415 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001416 if ((*rpc)->envp) {
1417 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001418 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1419 nc_err_set_msg(e, ly_errmsg(session->ctx), "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001420 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001421 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001422 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1423 * the server (RFC 6241 sec. 3) */
1424 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001425 } else {
1426 /* at least set the return value */
1427 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001428 }
Michal Vasko77367452021-02-16 16:32:18 +01001429 }
1430
1431cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001432 if (reply) {
1433 /* send error reply */
1434 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1435 nc_server_reply_free(reply);
1436 if (r != NC_MSG_REPLY) {
1437 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1438 if (session->status != NC_STATUS_INVALID) {
1439 session->status = NC_STATUS_INVALID;
1440 session->term_reason = NC_SESSION_TERM_OTHER;
1441 }
1442 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001443
1444 /* bad RPC and an error reply sent */
1445 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001446 }
1447
Michal Vasko77367452021-02-16 16:32:18 +01001448 ly_in_free(msg, 1);
1449 if (ret != NC_PSPOLL_RPC) {
1450 nc_server_rpc_free(*rpc);
1451 *rpc = NULL;
1452 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001453 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001454}
1455
fanchanghu966f2de2016-07-21 02:28:57 -04001456API void
1457nc_set_global_rpc_clb(nc_rpc_clb clb)
1458{
1459 global_rpc_clb = clb;
1460}
1461
Radek Krejci93e80222016-10-03 13:34:25 +02001462API NC_MSG_TYPE
1463nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1464{
Michal Vasko131120a2018-05-29 15:44:02 +02001465 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001466
1467 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001468 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001469 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001470 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001471 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001472 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001473 return NC_MSG_ERROR;
1474 }
1475
Michal Vasko131120a2018-05-29 15:44:02 +02001476 /* we do not need RPC lock for this, IO lock will be acquired properly */
1477 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001478 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001479 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001480 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001481
Michal Vasko131120a2018-05-29 15:44:02 +02001482 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001483}
1484
Michal Vaskof9467762023-03-28 09:02:08 +02001485/**
1486 * @brief Send a reply acquiring IO lock as needed.
1487 * Session RPC lock must be held!
1488 *
1489 * @param[in] session Session to use.
1490 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1491 * @param[in] rpc RPC to sent.
1492 * @return 0 on success.
1493 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1494 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001495 */
1496static int
Michal Vasko93224072021-11-09 12:14:28 +01001497nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001498{
1499 nc_rpc_clb clb;
1500 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001501 const struct lysc_node *rpc_act = NULL;
1502 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001503 int ret = 0;
1504 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001505
Michal Vasko4a827e52016-03-03 10:59:00 +01001506 if (!rpc) {
1507 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001508 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001509 }
1510
Michal Vasko77367452021-02-16 16:32:18 +01001511 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001512 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001513 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001514 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001515 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001516 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001517 if (elem->schema->nodetype == LYS_ACTION) {
1518 rpc_act = elem->schema;
1519 break;
1520 }
Michal Vasko77367452021-02-16 16:32:18 +01001521 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001522 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001523 if (!rpc_act) {
1524 ERRINT;
1525 return NC_PSPOLL_ERROR;
1526 }
1527 }
1528
1529 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001530 if (!global_rpc_clb) {
1531 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001532 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 +03001533 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001534 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001535 }
Michal Vasko428087d2016-01-14 16:04:28 +01001536 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001537 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001538 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001539 }
1540
1541 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001542 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001543 }
Michal Vasko77367452021-02-16 16:32:18 +01001544 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001545 if (reply->type == NC_RPL_ERROR) {
1546 ret |= NC_PSPOLL_REPLY_ERROR;
1547 }
1548 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001549
Michal Vasko131120a2018-05-29 15:44:02 +02001550 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001551 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001552 ret |= NC_PSPOLL_ERROR;
1553 }
Michal Vasko428087d2016-01-14 16:04:28 +01001554
1555 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1556 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1557 session->status = NC_STATUS_INVALID;
1558 }
1559
Michal Vasko71090fc2016-05-24 16:37:28 +02001560 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001561}
1562
Michal Vaskof9467762023-03-28 09:02:08 +02001563/**
1564 * @brief Poll a session from pspoll acquiring IO lock as needed.
1565 * Session must be running and session RPC lock held!
1566 *
1567 * @param[in] session Session to use.
1568 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1569 * @param[in] now_mono Current monotonic timestamp.
1570 * @param[in,out] msg Message to fill in case of an error.
1571 * @return NC_PSPOLL_RPC if some application data are available.
1572 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1573 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1574 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1575 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1576 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001577 */
1578static int
Michal Vasko131120a2018-05-29 15:44:02 +02001579nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001580{
Michal Vasko9a327362017-01-11 11:31:46 +01001581 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001582 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001583
romanf578cd52023-10-19 09:47:40 +02001584#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001585 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001586 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001587#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001588
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001589 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001590 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001591 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001592 sprintf(msg, "Session idle timeout elapsed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001593 session->status = NC_STATUS_INVALID;
1594 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1595 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1596 }
1597
Michal Vasko131120a2018-05-29 15:44:02 +02001598 r = nc_session_io_lock(session, io_timeout, __func__);
1599 if (r < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001600 sprintf(msg, "Session IO lock failed to be acquired");
Michal Vasko131120a2018-05-29 15:44:02 +02001601 return NC_PSPOLL_ERROR;
1602 } else if (!r) {
1603 return NC_PSPOLL_TIMEOUT;
1604 }
1605
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001606 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001607#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001608 case NC_TI_LIBSSH:
romanf578cd52023-10-19 09:47:40 +02001609 ssh_msg = ssh_message_get(session->ti.libssh.session);
1610 if (ssh_msg) {
1611 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1612 if (session->ti.libssh.next) {
1613 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1614 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1615 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1616 /* new NETCONF SSH channel */
1617 ret = NC_PSPOLL_SSH_CHANNEL;
1618 break;
1619 }
1620 }
1621 if (new != session) {
1622 ssh_message_free(ssh_msg);
1623 break;
1624 }
1625 }
1626 if (!ret) {
1627 /* just some SSH message */
1628 ret = NC_PSPOLL_SSH_MSG;
1629 }
1630 ssh_message_free(ssh_msg);
1631
1632 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1633 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1634 */
1635 break;
1636 }
1637
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001638 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001639 if (r == SSH_EOF) {
1640 sprintf(msg, "SSH channel unexpected EOF");
1641 session->status = NC_STATUS_INVALID;
1642 session->term_reason = NC_SESSION_TERM_DROPPED;
1643 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1644 } else if (r == SSH_ERROR) {
1645 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001646 session->status = NC_STATUS_INVALID;
1647 session->term_reason = NC_SESSION_TERM_OTHER;
1648 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001649 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001650 /* no application data received */
1651 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001652 } else {
1653 /* we have some application data */
1654 ret = NC_PSPOLL_RPC;
1655 }
1656 break;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001657 case NC_TI_OPENSSL:
1658 r = SSL_pending(session->ti.tls);
1659 if (!r) {
1660 /* no data pending in the SSL buffer, poll fd */
1661 pfd.fd = SSL_get_rfd(session->ti.tls);
1662 if (pfd.fd < 0) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001663 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001664 ret = NC_PSPOLL_ERROR;
1665 break;
1666 }
1667 pfd.events = POLLIN;
1668 pfd.revents = 0;
1669 r = poll(&pfd, 1, 0);
1670
1671 if ((r < 0) && (errno != EINTR)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001672 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001673 session->status = NC_STATUS_INVALID;
1674 ret = NC_PSPOLL_ERROR;
1675 } else if (r > 0) {
1676 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001677 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001678 session->status = NC_STATUS_INVALID;
1679 session->term_reason = NC_SESSION_TERM_DROPPED;
1680 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1681 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001682 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001683 session->status = NC_STATUS_INVALID;
1684 session->term_reason = NC_SESSION_TERM_OTHER;
1685 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1686 } else {
1687 ret = NC_PSPOLL_RPC;
1688 }
1689 } else {
1690 ret = NC_PSPOLL_TIMEOUT;
1691 }
1692 } else {
1693 ret = NC_PSPOLL_RPC;
1694 }
1695 break;
romanf578cd52023-10-19 09:47:40 +02001696#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001697 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001698 case NC_TI_UNIX:
1699 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001700 pfd.events = POLLIN;
1701 pfd.revents = 0;
1702 r = poll(&pfd, 1, 0);
1703
1704 if ((r < 0) && (errno != EINTR)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001705 sprintf(msg, "Poll failed (%s)", strerror(errno));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001706 session->status = NC_STATUS_INVALID;
1707 ret = NC_PSPOLL_ERROR;
1708 } else if (r > 0) {
1709 if (pfd.revents & (POLLHUP | POLLNVAL)) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001710 sprintf(msg, "Communication socket unexpectedly closed");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001711 session->status = NC_STATUS_INVALID;
1712 session->term_reason = NC_SESSION_TERM_DROPPED;
1713 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1714 } else if (pfd.revents & POLLERR) {
Michal Vasko4607daf2024-01-15 15:05:15 +01001715 sprintf(msg, "Communication socket error");
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001716 session->status = NC_STATUS_INVALID;
1717 session->term_reason = NC_SESSION_TERM_OTHER;
1718 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1719 } else {
1720 ret = NC_PSPOLL_RPC;
1721 }
1722 } else {
1723 ret = NC_PSPOLL_TIMEOUT;
1724 }
1725 break;
1726 case NC_TI_NONE:
Michal Vasko4607daf2024-01-15 15:05:15 +01001727 sprintf(msg, "Internal error (%s:%d)", __FILE__, __LINE__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001728 ret = NC_PSPOLL_ERROR;
1729 break;
1730 }
1731
Michal Vasko131120a2018-05-29 15:44:02 +02001732 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001733 return ret;
1734}
1735
1736API int
1737nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1738{
Michal Vasko443faa02022-10-20 09:09:03 +02001739 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001740 uint8_t q_id;
1741 uint16_t i, j;
1742 char msg[256];
1743 struct timespec ts_timeout, ts_cur;
1744 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001745 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001746 struct nc_server_rpc *rpc = NULL;
1747
romanf578cd52023-10-19 09:47:40 +02001748 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001749
Michal Vaskoade892d2017-02-22 13:40:35 +01001750 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001751 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001752 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001753 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001754
Michal Vaskoade892d2017-02-22 13:40:35 +01001755 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001756 nc_ps_unlock(ps, q_id, __func__);
1757 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001758 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001759
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001760 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001761 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001762 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001763 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001764 }
1765
1766 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001767 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001768 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001769 if (ps->last_event_session == ps->session_count - 1) {
1770 i = j = 0;
1771 } else {
1772 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001773 }
Michal Vasko9a327362017-01-11 11:31:46 +01001774 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001775 cur_ps_session = ps->sessions[i];
1776 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001777
Michal Vasko131120a2018-05-29 15:44:02 +02001778 /* SESSION RPC LOCK */
1779 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001780 if (r == -1) {
1781 ret = NC_PSPOLL_ERROR;
1782 } else if (r == 1) {
1783 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001784 switch (cur_ps_session->state) {
1785 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001786 if (cur_session->status == NC_STATUS_RUNNING) {
1787 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001788 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001789
Michal Vasko131120a2018-05-29 15:44:02 +02001790 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001791 switch (ret) {
1792 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001793 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001794 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001795 break;
1796 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001797 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001798 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001799 break;
1800 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001801#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001802 case NC_PSPOLL_SSH_CHANNEL:
1803 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001804#endif /* NC_ENABLED_SSH_TLS */
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_RPC:
1808 /* let's keep the state busy, we are not done with this session */
1809 break;
1810 }
1811 } else {
1812 /* session is not fine, let the caller know */
1813 ret = NC_PSPOLL_SESSION_TERM;
1814 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1815 ret |= NC_PSPOLL_SESSION_ERROR;
1816 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001817 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001818 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001819 break;
1820 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001821 /* it definitely should not be busy because we have the lock */
1822 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001823 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001824 break;
1825 case NC_PS_STATE_INVALID:
1826 /* we got it locked, but it will be freed, let it be */
1827 ret = NC_PSPOLL_TIMEOUT;
1828 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001829 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001830
Michal Vasko131120a2018-05-29 15:44:02 +02001831 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001832 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001833 /* SESSION RPC UNLOCK */
1834 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001835 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001836 } else {
1837 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001838 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001839 }
Michal Vasko428087d2016-01-14 16:04:28 +01001840
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001841 /* something happened */
1842 if (ret != NC_PSPOLL_TIMEOUT) {
1843 break;
1844 }
1845
Michal Vasko9a327362017-01-11 11:31:46 +01001846 if (i == ps->session_count - 1) {
1847 i = 0;
1848 } else {
1849 ++i;
1850 }
1851 } while (i != j);
1852
Michal Vaskoade892d2017-02-22 13:40:35 +01001853 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001854 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001855 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001856
Michal Vaskod8a74192023-02-06 15:51:50 +01001857 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001858 /* final timeout */
1859 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001860 }
Michal Vasko428087d2016-01-14 16:04:28 +01001861 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001862 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001863
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001864 /* do we want to return the session? */
1865 switch (ret) {
1866 case NC_PSPOLL_RPC:
1867 case NC_PSPOLL_SESSION_TERM:
1868 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001869#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001870 case NC_PSPOLL_SSH_CHANNEL:
1871 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001872#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001873 if (session) {
1874 *session = cur_session;
1875 }
1876 ps->last_event_session = i;
1877 break;
1878 default:
1879 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001880 }
Michal Vasko428087d2016-01-14 16:04:28 +01001881
Michal Vaskoade892d2017-02-22 13:40:35 +01001882 /* PS UNLOCK */
1883 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001884
Michal Vasko131120a2018-05-29 15:44:02 +02001885 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001886 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001887 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001888 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1889 if (cur_session->status != NC_STATUS_RUNNING) {
1890 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001891 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001892 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001893 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001894 }
1895 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001896 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001897
Michal Vasko7f1ee932018-10-11 09:41:42 +02001898 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001899 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001900 if (cur_session->status != NC_STATUS_RUNNING) {
1901 ret |= NC_PSPOLL_SESSION_TERM;
1902 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1903 ret |= NC_PSPOLL_SESSION_ERROR;
1904 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001905 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001906 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001907 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001908 }
Michal Vasko428087d2016-01-14 16:04:28 +01001909 }
Michal Vasko77367452021-02-16 16:32:18 +01001910 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001911
Michal Vasko131120a2018-05-29 15:44:02 +02001912 /* SESSION RPC UNLOCK */
1913 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001914 }
1915
Michal Vasko48a63ed2016-03-01 09:48:21 +01001916 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001917}
1918
Michal Vaskod09eae62016-02-01 10:32:52 +01001919API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001920nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001921{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001922 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001923 uint16_t i;
1924 struct nc_session *session;
1925
Michal Vasko9a25e932016-02-01 10:36:42 +01001926 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001927 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001928 return;
1929 }
1930
Michal Vasko48a63ed2016-03-01 09:48:21 +01001931 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001932 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001933 return;
1934 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001935
Michal Vasko48a63ed2016-03-01 09:48:21 +01001936 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001937 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001938 nc_session_free(ps->sessions[i]->session, data_free);
1939 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001940 }
1941 free(ps->sessions);
1942 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001943 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001944 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001945 } else {
1946 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001947 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1948 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001949 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001950 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001951 continue;
1952 }
1953
1954 ++i;
1955 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001956 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001957
1958 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001959 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001960}
1961
romanfb3f7cf2023-11-30 16:10:09 +01001962int
1963nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1964{
1965 int sock = -1, set_addr, ret = 0;
1966
1967 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1968
1969 if (address) {
1970 set_addr = 1;
1971 } else {
1972 set_addr = 0;
1973 }
1974
1975 if (set_addr) {
1976 port = bind->port;
1977 } else {
1978 address = bind->address;
1979 }
1980
1981 /* we have all the information we need to create a listening socket */
1982 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1983 /* create new socket, close the old one */
1984 if (endpt->ti == NC_TI_UNIX) {
1985 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1986 } else {
1987 sock = nc_sock_listen_inet(address, port, &endpt->ka);
1988 }
1989
1990 if (sock == -1) {
1991 ret = 1;
1992 goto cleanup;
1993 }
1994
1995 if (bind->sock > -1) {
1996 close(bind->sock);
1997 }
1998 bind->sock = sock;
1999 }
2000
2001 if (sock > -1) {
2002 switch (endpt->ti) {
2003 case NC_TI_UNIX:
2004 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
2005 break;
2006#ifdef NC_ENABLED_SSH_TLS
2007 case NC_TI_LIBSSH:
2008 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2009 break;
2010 case NC_TI_OPENSSL:
2011 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2012 break;
2013#endif /* NC_ENABLED_SSH_TLS */
2014 default:
2015 ERRINT;
2016 ret = 1;
2017 break;
2018 }
2019 }
2020
2021cleanup:
2022 return ret;
2023}
2024
Michal Vasko6f865982023-11-21 12:10:42 +01002025/**
2026 * @brief Get UID of the owner of a socket.
2027 *
2028 * @param[in] sock Socket to analyze.
2029 * @param[out] uid Socket owner UID.
2030 * @return 0 on success,
2031 * @return -1 on error.
2032 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002033static int
apropp-molex4e903c32020-04-20 03:06:58 -04002034nc_get_uid(int sock, uid_t *uid)
2035{
Michal Vasko6f865982023-11-21 12:10:42 +01002036 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002037
Michal Vaskod3910912020-04-20 09:12:49 +02002038#ifdef SO_PEERCRED
2039 struct ucred ucred;
2040 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002041
Michal Vaskod3910912020-04-20 09:12:49 +02002042 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002043 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2044 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002045 *uid = ucred.uid;
2046 }
2047#else
Michal Vasko6f865982023-11-21 12:10:42 +01002048 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002049#endif
2050
Michal Vasko6f865982023-11-21 12:10:42 +01002051 if (r < 0) {
2052 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002053 return -1;
2054 }
apropp-molex4e903c32020-04-20 03:06:58 -04002055 return 0;
2056}
2057
2058static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002059nc_accept_unix(struct nc_session *session, int sock)
2060{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002061#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002062 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002063 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002064
Michal Vasko5f352c52019-07-10 16:12:06 +02002065 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002066 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002067 char *buf = NULL;
2068 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002069
Michal Vaskod3910912020-04-20 09:12:49 +02002070 if (nc_get_uid(sock, &uid)) {
2071 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002072 return -1;
2073 }
2074
romanf6e32012023-04-24 15:51:26 +02002075 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002076 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002077 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002078 close(sock);
2079 return -1;
2080 }
2081
2082 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002083 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002084 if (username == NULL) {
2085 ERRMEM;
2086 close(sock);
2087 return -1;
2088 }
Michal Vasko93224072021-11-09 12:14:28 +01002089 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002090
2091 session->ti.unixsock.sock = sock;
2092
2093 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002094#else
2095 return -1;
2096#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002097}
2098
Michal Vaskoe2713da2016-08-22 16:06:40 +02002099API int
romanfb3f7cf2023-11-30 16:10:09 +01002100nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2101{
2102 int ret = 0;
2103 void *tmp;
2104 uint16_t i;
2105
2106 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2107
2108 /* CONFIG LOCK */
2109 pthread_rwlock_wrlock(&server_opts.config_lock);
2110
2111 /* check name uniqueness */
2112 for (i = 0; i < server_opts.endpt_count; i++) {
2113 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2114 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2115 ret = 1;
2116 goto cleanup;
2117 }
2118 }
2119
2120 /* alloc a new endpoint */
2121 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2122 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2123 server_opts.endpts = tmp;
2124 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2125
2126 /* alloc a new bind */
2127 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2128 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2129 server_opts.binds = tmp;
2130 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2131 server_opts.binds[server_opts.endpt_count].sock = -1;
2132 server_opts.endpt_count++;
2133
2134 /* set name and ti */
2135 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2136 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2137 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2138
2139 /* set the bind data */
2140 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2141 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2142
2143 /* alloc unix opts */
2144 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2145 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2146
2147 /* set the opts data */
2148 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2149 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2150 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2151 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2152 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2153
2154 /* start listening */
2155 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2156 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2157 if (ret) {
2158 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2159 goto cleanup;
2160 }
2161
2162cleanup:
2163 /* CONFIG UNLOCK */
2164 pthread_rwlock_unlock(&server_opts.config_lock);
2165 return ret;
2166}
2167
2168static void
2169nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2170{
2171 if (bind->sock > -1) {
2172 close(bind->sock);
2173 }
2174
2175 unlink(bind->address);
2176 free(bind->address);
2177 free(opts->address);
2178
2179 free(opts);
2180}
2181
2182void
2183_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2184{
2185 free(endpt->name);
2186 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2187
2188 server_opts.endpt_count--;
2189 if (!server_opts.endpt_count) {
2190 free(server_opts.endpts);
2191 free(server_opts.binds);
2192 server_opts.endpts = NULL;
2193 server_opts.binds = NULL;
2194 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2195 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2196 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2197 }
2198}
2199
2200API void
2201nc_server_del_endpt_unix_socket(const char *endpt_name)
2202{
2203 uint16_t i;
2204 struct nc_endpt *endpt = NULL;
2205 struct nc_bind *bind;
2206
romanb6ad37a2023-12-07 13:08:46 +01002207 NC_CHECK_ARG_RET(NULL, endpt_name, );
2208
romanfb3f7cf2023-11-30 16:10:09 +01002209 /* CONFIG LOCK */
2210 pthread_rwlock_wrlock(&server_opts.config_lock);
2211
romanfb3f7cf2023-11-30 16:10:09 +01002212 for (i = 0; i < server_opts.endpt_count; i++) {
2213 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2214 endpt = &server_opts.endpts[i];
2215 bind = &server_opts.binds[i];
2216 break;
2217 }
2218 }
2219 if (!endpt) {
2220 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2221 goto end;
2222 }
2223 if (endpt->ti != NC_TI_UNIX) {
2224 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2225 goto end;
2226 }
2227
2228 _nc_server_del_endpt_unix_socket(endpt, bind);
2229
2230end:
2231 /* CONFIG UNLOCK */
2232 pthread_rwlock_unlock(&server_opts.config_lock);
2233}
2234
2235API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002236nc_server_endpt_count(void)
2237{
2238 return server_opts.endpt_count;
2239}
2240
Michal Vasko71090fc2016-05-24 16:37:28 +02002241API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002242nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002243{
Michal Vasko71090fc2016-05-24 16:37:28 +02002244 NC_MSG_TYPE msgtype;
Michal Vasko1a38c862016-01-15 15:50:07 +01002245 int sock, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002246 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002247 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002248 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002249
romanf578cd52023-10-19 09:47:40 +02002250 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002251
Michal Vasko93224072021-11-09 12:14:28 +01002252 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002253 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002254
romanf578cd52023-10-19 09:47:40 +02002255 /* CONFIG LOCK */
2256 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002257
2258 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002259 ERR(NULL, "No endpoints to accept sessions on.");
romanf578cd52023-10-19 09:47:40 +02002260 /* CONFIG UNLOCK */
2261 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002262 return NC_MSG_ERROR;
Michal Vasko51e514d2016-02-02 15:51:52 +01002263 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002264
romanf578cd52023-10-19 09:47:40 +02002265 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 +01002266 if (ret < 1) {
Michal Vaskob737d752016-02-09 09:01:27 +01002267 free(host);
romanf578cd52023-10-19 09:47:40 +02002268 /* CONFIG UNLOCK */
2269 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko5e203472016-05-30 15:27:58 +02002270 if (!ret) {
2271 return NC_MSG_WOULDBLOCK;
2272 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002273 return NC_MSG_ERROR;
Michal Vasko9e036d52016-01-08 10:49:26 +01002274 }
Michal Vaskoade892d2017-02-22 13:40:35 +01002275
Michal Vaskob48aa812016-01-18 14:13:09 +01002276 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002277
Michal Vasko131120a2018-05-29 15:44:02 +02002278 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002279 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(host); msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002280 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002281 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002282 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002283 (*session)->host = host;
Michal Vasko1a38c862016-01-15 15:50:07 +01002284 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002285
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002286 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002287#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002288 if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002289 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko71090fc2016-05-24 16:37:28 +02002290 if (ret < 0) {
2291 msgtype = NC_MSG_ERROR;
2292 goto cleanup;
2293 } else if (!ret) {
2294 msgtype = NC_MSG_WOULDBLOCK;
2295 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002296 }
romanf578cd52023-10-19 09:47:40 +02002297 } else if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002298 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002299 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko71090fc2016-05-24 16:37:28 +02002300 if (ret < 0) {
2301 msgtype = NC_MSG_ERROR;
2302 goto cleanup;
2303 } else if (!ret) {
2304 msgtype = NC_MSG_WOULDBLOCK;
2305 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002306 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002307 } else
romanf578cd52023-10-19 09:47:40 +02002308#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002309 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2310 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2311 ret = nc_accept_unix(*session, sock);
2312 if (ret < 0) {
2313 msgtype = NC_MSG_ERROR;
2314 goto cleanup;
2315 }
2316 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002317 ERRINT;
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002318 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002319 msgtype = NC_MSG_ERROR;
2320 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002321 }
2322
Michal Vasko2cc4c682016-03-01 09:16:48 +01002323 (*session)->data = NULL;
2324
romanf578cd52023-10-19 09:47:40 +02002325 /* CONFIG UNLOCK */
2326 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002327
Michal Vaskob48aa812016-01-18 14:13:09 +01002328 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002329 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002330
Michal Vasko9e036d52016-01-08 10:49:26 +01002331 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002332 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002333 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002334 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002335 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002336 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002337 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002338
Michal Vaskod8a74192023-02-06 15:51:50 +01002339 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002340 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002341 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002342 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002343 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002344
Michal Vasko71090fc2016-05-24 16:37:28 +02002345 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002346
Michal Vasko71090fc2016-05-24 16:37:28 +02002347cleanup:
romanf578cd52023-10-19 09:47:40 +02002348 /* CONFIG UNLOCK */
2349 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002350
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002351 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002352 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002353 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002354}
2355
romanf578cd52023-10-19 09:47:40 +02002356#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002357
2358API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002359nc_server_ch_is_client(const char *name)
2360{
2361 uint16_t i;
2362 int found = 0;
2363
2364 if (!name) {
2365 return found;
2366 }
2367
2368 /* READ LOCK */
2369 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2370
2371 /* check name uniqueness */
2372 for (i = 0; i < server_opts.ch_client_count; ++i) {
2373 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2374 found = 1;
2375 break;
2376 }
2377 }
2378
2379 /* UNLOCK */
2380 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2381
2382 return found;
2383}
2384
2385API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002386nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2387{
2388 uint16_t i;
2389 struct nc_ch_client *client = NULL;
2390 int found = 0;
2391
2392 if (!client_name || !endpt_name) {
2393 return found;
2394 }
2395
2396 /* READ LOCK */
2397 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2398
2399 for (i = 0; i < server_opts.ch_client_count; ++i) {
2400 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2401 client = &server_opts.ch_clients[i];
2402 break;
2403 }
2404 }
2405
2406 if (!client) {
2407 goto cleanup;
2408 }
2409
2410 for (i = 0; i < client->ch_endpt_count; ++i) {
2411 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2412 found = 1;
2413 break;
2414 }
2415 }
2416
2417cleanup:
2418 /* UNLOCK */
2419 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2420 return found;
2421}
2422
Michal Vasko056f53c2022-10-21 13:38:15 +02002423/**
2424 * @brief Create a connection for an endpoint.
2425 *
2426 * Client lock is expected to be held.
2427 *
2428 * @param[in] endpt Endpoint to use.
2429 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2430 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2431 * @param[in] ctx_cb_data Context callbacks data.
2432 * @param[out] session Created NC session.
2433 * @return NC_MSG values.
2434 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002435static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002436nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2437 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 +01002438{
Michal Vasko71090fc2016-05-24 16:37:28 +02002439 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002440 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002441 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002442 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002443 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002444
Michal Vasko056f53c2022-10-21 13:38:15 +02002445 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 +01002446 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002447 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002448 }
2449
Michal Vasko93224072021-11-09 12:14:28 +01002450 /* acquire context */
2451 ctx = acquire_ctx_cb(ctx_cb_data);
2452 if (!ctx) {
2453 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2454 close(sock);
2455 free(ip_host);
2456 return NC_MSG_ERROR;
2457 }
2458
romanf578cd52023-10-19 09:47:40 +02002459 /* init ctx as needed */
2460 nc_server_init_cb_ctx(ctx);
2461
Michal Vasko93224072021-11-09 12:14:28 +01002462 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002463 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002464 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002465 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002466 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002467 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002468 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002469 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002470
Michal Vaskob05053d2016-01-22 16:12:06 +01002471 /* sock gets assigned to session or closed */
Michal Vaskoadf30f02019-06-24 09:34:47 +02002472 if (endpt->ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002473 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002474 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002475
Michal Vasko71090fc2016-05-24 16:37:28 +02002476 if (ret < 0) {
2477 msgtype = NC_MSG_ERROR;
2478 goto fail;
2479 } else if (!ret) {
2480 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002481 goto fail;
2482 }
romanf578cd52023-10-19 09:47:40 +02002483 } else if (endpt->ti == NC_TI_OPENSSL) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002484 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002485 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002486 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002487
Michal Vasko71090fc2016-05-24 16:37:28 +02002488 if (ret < 0) {
2489 msgtype = NC_MSG_ERROR;
2490 goto fail;
2491 } else if (!ret) {
2492 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002493 goto fail;
2494 }
roman423cc0d2023-11-24 11:29:47 +01002495 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002496 ERRINT;
2497 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002498 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002499 goto fail;
2500 }
2501
2502 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002503 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002504
2505 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002506 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002507 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002508 goto fail;
2509 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002510
Michal Vaskod8a74192023-02-06 15:51:50 +01002511 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002512 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002513 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002514 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002515 (*session)->status = NC_STATUS_RUNNING;
2516
Michal Vasko71090fc2016-05-24 16:37:28 +02002517 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002518
2519fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002520 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002521 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002522 if (ctx) {
2523 release_ctx_cb(ctx_cb_data);
2524 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002525 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002526}
2527
Michal Vasko6f865982023-11-21 12:10:42 +01002528/**
2529 * @brief Wait for any event after a NC session was established on a CH client.
2530 *
Michal Vasko6f865982023-11-21 12:10:42 +01002531 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002532 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002533 * @return 0 if session was terminated normally,
2534 * @return 1 if the CH client was removed,
2535 * @return -1 on error.
2536 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002537static int
roman8341e8b2023-11-23 16:12:42 +01002538nc_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 +02002539{
Michal Vasko6f865982023-11-21 12:10:42 +01002540 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002541 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002542 struct timespec ts;
2543 struct nc_ch_client *client;
2544
Michal Vasko2e6defd2016-10-07 15:48:15 +02002545 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002546 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002547
Michal Vaskofeccb312022-03-24 15:24:59 +01002548 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002549
Michal Vasko2e6defd2016-10-07 15:48:15 +02002550 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002551 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002552 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002553 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002554
2555 /* CH UNLOCK */
2556 pthread_mutex_unlock(&session->opts.server.ch_lock);
2557
Michal Vasko77d56d72022-09-07 10:30:48 +02002558 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002559 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002560 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002561 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002562 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002563
2564 do {
romanf578cd52023-10-19 09:47:40 +02002565 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002566
Michal Vasko0db3db52021-03-03 10:45:42 +01002567 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002568 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 +01002569 if (!r) {
2570 /* we were woken up, something probably happened */
2571 if (session->status != NC_STATUS_RUNNING) {
2572 break;
2573 }
2574 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002575 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002576 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002577 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002578 }
2579
Michal Vasko2e6defd2016-10-07 15:48:15 +02002580 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002581
Michal Vasko2e6defd2016-10-07 15:48:15 +02002582 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002583 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002584 if (!client) {
2585 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002586 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002587 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002588 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002589 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002590 }
2591
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002592 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002593 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002594 } else {
2595 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002596 }
2597
Michal Vaskod8a74192023-02-06 15:51:50 +01002598 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002599 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 +02002600 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002601 session->status = NC_STATUS_INVALID;
2602 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2603 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002604
2605 /* UNLOCK */
2606 nc_server_ch_client_unlock(client);
2607
2608 } while (session->status == NC_STATUS_RUNNING);
2609
Michal Vaskofeccb312022-03-24 15:24:59 +01002610 /* signal to nc_session_free() that CH thread is terminating */
2611 session->flags &= ~NC_SESSION_CH_THREAD;
2612 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002613
Michal Vasko27377422018-03-15 08:59:35 +01002614 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002615 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002616
Michal Vasko6f865982023-11-21 12:10:42 +01002617 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002618}
2619
romanf578cd52023-10-19 09:47:40 +02002620/**
2621 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2622 *
2623 * @param[in] session An established session.
2624 * @param[in] data Call Home thread's data.
2625 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2626 *
2627 * @return 0 if the thread should stop running, 1 if it should continue.
2628 */
2629static int
2630nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2631{
2632 struct timespec ts;
2633 int ret = 0, thread_running;
2634
2635 /* COND LOCK */
2636 pthread_mutex_lock(&data->cond_lock);
2637 /* get reconnect timeout in ms */
2638 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2639 while (!ret && data->thread_running) {
2640 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2641 }
2642
2643 thread_running = data->thread_running;
2644 /* COND UNLOCK */
2645 pthread_mutex_unlock(&data->cond_lock);
2646
2647 if (!thread_running) {
2648 /* thread is terminating */
2649 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2650 ret = 0;
2651 } else if (ret == ETIMEDOUT) {
2652 /* time to reconnect */
2653 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2654 ret = 1;
2655 } else if (ret) {
2656 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2657 ret = 0;
2658 }
2659
2660 return ret;
2661}
2662
2663/**
2664 * @brief Checks if a Call Home thread should terminate.
2665 *
2666 * Checks the shared boolean variable thread_running. This should be done everytime
2667 * before entering a critical section.
2668 *
2669 * @param[in] data Call Home thread's data.
2670 *
2671 * @return 0 if the thread should stop running, -1 if it can continue.
2672 */
2673static int
2674nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2675{
2676 int ret = -1;
2677
2678 /* COND LOCK */
2679 pthread_mutex_lock(&data->cond_lock);
2680 if (!data->thread_running) {
2681 /* thread should stop running */
2682 ret = 0;
2683 }
2684 /* COND UNLOCK */
2685 pthread_mutex_unlock(&data->cond_lock);
2686
2687 return ret;
2688}
2689
Michal Vasko6f865982023-11-21 12:10:42 +01002690/**
2691 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2692 *
2693 * @param[in] name Name of the CH client.
2694 * @return Pointer to the CH client.
2695 */
2696static struct nc_ch_client *
2697nc_server_ch_client_with_endpt_lock(const char *name)
2698{
2699 struct nc_ch_client *client;
2700
2701 while (1) {
2702 /* LOCK */
2703 client = nc_server_ch_client_lock(name);
2704 if (!client) {
2705 return NULL;
2706 }
2707 if (client->ch_endpt_count) {
2708 return client;
2709 }
2710 /* no endpoints defined yet */
2711
2712 /* UNLOCK */
2713 nc_server_ch_client_unlock(client);
2714
2715 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2716 }
2717
2718 return NULL;
2719}
2720
2721/**
2722 * @brief Call Home client management thread.
2723 *
2724 * @param[in] arg CH client thread argument.
2725 * @return NULL.
2726 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002727static void *
2728nc_ch_client_thread(void *arg)
2729{
Michal Vasko6f865982023-11-21 12:10:42 +01002730 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002731 NC_MSG_TYPE msgtype;
2732 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002733 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002734 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002735 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002736 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002737 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002738 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002739
2740 /* LOCK */
2741 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002742 if (!client) {
2743 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2744 goto cleanup;
2745 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002746
2747 cur_endpt = &client->ch_endpts[0];
2748 cur_endpt_name = strdup(cur_endpt->name);
2749
Michal Vasko6f865982023-11-21 12:10:42 +01002750 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002751 if (!cur_attempts) {
2752 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2753 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002754
Michal Vasko6f865982023-11-21 12:10:42 +01002755 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 +02002756 if (msgtype == NC_MSG_HELLO) {
2757 /* UNLOCK */
2758 nc_server_ch_client_unlock(client);
2759
romanf578cd52023-10-19 09:47:40 +02002760 if (!nc_server_ch_client_thread_is_running(data)) {
2761 /* thread should stop running */
2762 goto cleanup;
2763 }
2764
2765 /* run while the session is established */
2766 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002767 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002768 goto cleanup;
2769 }
roman8341e8b2023-11-23 16:12:42 +01002770 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002771
roman8341e8b2023-11-23 16:12:42 +01002772 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002773 if (!nc_server_ch_client_thread_is_running(data)) {
2774 /* thread should stop running */
2775 goto cleanup;
2776 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002777
2778 /* LOCK */
2779 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002780 if (!client) {
2781 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2782 goto cleanup;
2783 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002784
2785 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002786 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002787 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002788 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002789 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002790 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002791 } else {
2792 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002793 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002794 }
2795
Michal Vasko2e6defd2016-10-07 15:48:15 +02002796 /* UNLOCK */
2797 nc_server_ch_client_unlock(client);
2798
romanf578cd52023-10-19 09:47:40 +02002799 /* wait for the timeout to elapse, so we can try to reconnect */
2800 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2801 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2802 goto cleanup;
2803 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002804
2805 /* LOCK */
2806 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002807 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002808 }
2809
2810 /* set next endpoint to try */
2811 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002812 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002813 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002814 /* we keep the current one but due to unlock/lock we have to find it again */
2815 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2816 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2817 break;
2818 }
2819 }
2820 if (next_endpt_index >= client->ch_endpt_count) {
2821 /* endpoint was removed, start with the first one */
2822 next_endpt_index = 0;
2823 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002824 } else {
2825 /* just get a random index */
2826 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002827 }
2828
Michal Vasko2e6defd2016-10-07 15:48:15 +02002829 } else {
romanf578cd52023-10-19 09:47:40 +02002830 /* session was not created, wait a little bit and try again */
2831 max_wait = client->max_wait;
2832
Michal Vasko6bb116b2016-10-26 13:53:46 +02002833 /* UNLOCK */
2834 nc_server_ch_client_unlock(client);
2835
romanf578cd52023-10-19 09:47:40 +02002836 /* wait for max_wait seconds */
2837 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2838 /* thread should stop running */
2839 goto cleanup;
2840 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002841
Michal Vasko6bb116b2016-10-26 13:53:46 +02002842 /* LOCK */
2843 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002844 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002845
Michal Vasko2e6defd2016-10-07 15:48:15 +02002846 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002847
2848 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002849 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2850 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002851 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002852 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002853 }
2854
Peter Feiged05f2252018-09-03 08:09:47 +00002855 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002856 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002857 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002858 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002859 cur_attempts = 0;
2860 } else if (cur_attempts == client->max_attempts) {
2861 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002862 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002863 data->client_name, cur_endpt_name, client->max_attempts);
2864
2865 /* clear a pending socket, if any */
2866 cur_endpt = &client->ch_endpts[next_endpt_index];
2867 if (cur_endpt->sock_pending > -1) {
2868 close(cur_endpt->sock_pending);
2869 cur_endpt->sock_pending = -1;
2870 }
2871
Peter Feiged05f2252018-09-03 08:09:47 +00002872 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002873 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002874 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002875 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002876 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002877 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002878 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002879 cur_attempts = 0;
2880 } /* else we keep the current one */
2881 }
Peter Feiged05f2252018-09-03 08:09:47 +00002882
2883 cur_endpt = &client->ch_endpts[next_endpt_index];
2884 free(cur_endpt_name);
2885 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002886 }
Michal Vasko6f865982023-11-21 12:10:42 +01002887
romanf578cd52023-10-19 09:47:40 +02002888 /* UNLOCK if we break out of the loop */
2889 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002890
2891cleanup:
romanf578cd52023-10-19 09:47:40 +02002892 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002893 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002894 free(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002895 pthread_cond_destroy(&data->cond);
2896 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002897 free(data);
2898 return NULL;
2899}
2900
2901API int
Michal Vasko93224072021-11-09 12:14:28 +01002902nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002903 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2904 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002905{
Michal Vasko6f865982023-11-21 12:10:42 +01002906 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002907 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002908 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002909 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002910
romanf578cd52023-10-19 09:47:40 +02002911 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2912
Michal Vasko6f865982023-11-21 12:10:42 +01002913 /* LOCK */
2914 ch_client = nc_server_ch_client_lock(client_name);
2915 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002916 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002917 return -1;
2918 }
2919
Michal Vasko6f865982023-11-21 12:10:42 +01002920 /* create the thread argument */
2921 arg = calloc(1, sizeof *arg);
2922 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002923 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002924 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002925 arg->acquire_ctx_cb = acquire_ctx_cb;
2926 arg->release_ctx_cb = release_ctx_cb;
2927 arg->ctx_cb_data = ctx_cb_data;
2928 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002929 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002930 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002931 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002932
Michal Vasko6f865982023-11-21 12:10:42 +01002933 /* creating the thread */
2934 arg->thread_running = 1;
2935 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2936 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2937 rc = -1;
2938 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002939 }
Michal Vasko6f865982023-11-21 12:10:42 +01002940
Michal Vasko2e6defd2016-10-07 15:48:15 +02002941 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002942 ch_client->tid = tid;
2943 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002944 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002945
Michal Vasko6f865982023-11-21 12:10:42 +01002946cleanup:
2947 /* UNLOCK */
2948 nc_server_ch_client_unlock(ch_client);
2949
2950 if (arg) {
2951 free(arg->client_name);
2952 free(arg);
2953 }
2954 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002955}
2956
romanf578cd52023-10-19 09:47:40 +02002957#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002958
roman44efa322023-11-03 13:57:25 +01002959API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002960nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002961{
roman44efa322023-11-03 13:57:25 +01002962 struct timespec fail = {0};
2963
2964 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002965
2966 if (session->side != NC_SERVER) {
2967 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002968 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002969 }
2970
Michal Vasko2e6defd2016-10-07 15:48:15 +02002971 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002972}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002973
2974API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002975nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002976{
2977 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002978 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002979 return;
2980 }
2981
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002982 /* NTF STATUS LOCK */
2983 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
2984
Michal Vasko71dbd772021-03-23 14:08:37 +01002985 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002986
2987 /* NTF STATUS UNLOCK */
2988 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01002989}
2990
2991API void
2992nc_session_dec_notif_status(struct nc_session *session)
2993{
2994 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002995 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01002996 return;
2997 }
2998
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002999 /* NTF STATUS LOCK */
3000 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
3001
Michal Vasko71dbd772021-03-23 14:08:37 +01003002 if (session->opts.server.ntf_status) {
3003 --session->opts.server.ntf_status;
3004 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003005
3006 /* NTF STATUS UNLOCK */
3007 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01003008}
3009
3010API int
3011nc_session_get_notif_status(const struct nc_session *session)
3012{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003013 uint32_t ntf_status;
3014
Michal Vasko3486a7c2017-03-03 13:28:07 +01003015 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003016 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003017 return 0;
3018 }
3019
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003020 /* NTF STATUS LOCK */
3021 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3022
3023 ntf_status = session->opts.server.ntf_status;
3024
3025 /* NTF STATUS UNLOCK */
3026 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3027
3028 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003029}