blob: 1967c79e8bab2d1b5443c0780ad72698daee8137 [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 Vaskob48aa812016-01-18 14:13:09 +010056};
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +010057
fanchanghu966f2de2016-07-21 02:28:57 -040058static nc_rpc_clb global_rpc_clb = NULL;
59
roman423cc0d2023-11-24 11:29:47 +010060#ifdef NC_ENABLED_SSH_TLS
Michal Vasko6f865982023-11-21 12:10:42 +010061/**
62 * @brief Lock CH client structures for reading and lock the specific client.
63 *
64 * @param[in] name Name of the CH client.
65 * @return CH client, NULL if not found.
66 */
67static struct nc_ch_client *
68nc_server_ch_client_lock(const char *name)
Michal Vasko3031aae2016-01-27 16:07:18 +010069{
70 uint16_t i;
Michal Vasko2e6defd2016-10-07 15:48:15 +020071 struct nc_ch_client *client = NULL;
Michal Vaskoadf30f02019-06-24 09:34:47 +020072
Michal Vasko6f865982023-11-21 12:10:42 +010073 assert(name);
Michal Vaskoddce1212019-05-24 09:58:49 +020074
Michal Vasko2e6defd2016-10-07 15:48:15 +020075 /* READ LOCK */
76 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
77
78 for (i = 0; i < server_opts.ch_client_count; ++i) {
romanf578cd52023-10-19 09:47:40 +020079 if (server_opts.ch_clients[i].name && !strcmp(server_opts.ch_clients[i].name, name)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +020080 client = &server_opts.ch_clients[i];
81 break;
82 }
83 }
84
85 if (!client) {
Michal Vaskoadf30f02019-06-24 09:34:47 +020086 /* READ UNLOCK */
87 pthread_rwlock_unlock(&server_opts.ch_client_lock);
88 } else {
89 /* CH CLIENT LOCK */
90 pthread_mutex_lock(&client->lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +020091 }
92
Michal Vasko6f865982023-11-21 12:10:42 +010093 return client;
Michal Vasko2e6defd2016-10-07 15:48:15 +020094}
95
Michal Vasko6f865982023-11-21 12:10:42 +010096/**
97 * @brief Unlock CH client strcutures and the specific client.
98 *
99 * @param[in] endpt Locked CH client structure.
100 */
101static void
Michal Vasko2e6defd2016-10-07 15:48:15 +0200102nc_server_ch_client_unlock(struct nc_ch_client *client)
103{
104 /* CH CLIENT UNLOCK */
105 pthread_mutex_unlock(&client->lock);
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +0100106
107 /* READ UNLOCK */
Michal Vasko2e6defd2016-10-07 15:48:15 +0200108 pthread_rwlock_unlock(&server_opts.ch_client_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +0100109}
Michal Vasko086311b2016-01-08 09:53:11 +0100110
roman423cc0d2023-11-24 11:29:47 +0100111#endif /* NC_ENABLED_SSH_TLS */
112
roman78df0fa2023-11-02 10:33:57 +0100113int
114nc_server_get_referenced_endpt(const char *name, struct nc_endpt **endpt)
115{
116 uint16_t i;
117
118 for (i = 0; i < server_opts.endpt_count; i++) {
119 if (!strcmp(name, server_opts.endpts[i].name)) {
120 *endpt = &server_opts.endpts[i];
121 return 0;
122 }
123 }
124
125 ERR(NULL, "Referenced endpoint \"%s\" was not found.", name);
126 return 1;
127}
128
Michal Vasko1a38c862016-01-15 15:50:07 +0100129API void
130nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
131{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200132 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200133 ERRARG(session, "session");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200134 return;
135 } else if (!reason) {
romanf578cd52023-10-19 09:47:40 +0200136 ERRARG(session, "reason");
Michal Vasko1a38c862016-01-15 15:50:07 +0100137 return;
138 }
139
Michal Vasko142cfea2017-08-07 10:12:11 +0200140 if ((reason != NC_SESSION_TERM_KILLED) && (session->term_reason == NC_SESSION_TERM_KILLED)) {
141 session->killed_by = 0;
142 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100143 session->term_reason = reason;
144}
145
Michal Vasko142cfea2017-08-07 10:12:11 +0200146API void
147nc_session_set_killed_by(struct nc_session *session, uint32_t sid)
148{
149 if (!session || (session->term_reason != NC_SESSION_TERM_KILLED)) {
romanf578cd52023-10-19 09:47:40 +0200150 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200151 return;
152 } else if (!sid) {
romanf578cd52023-10-19 09:47:40 +0200153 ERRARG(session, "sid");
Michal Vasko142cfea2017-08-07 10:12:11 +0200154 return;
155 }
156
157 session->killed_by = sid;
158}
159
160API void
161nc_session_set_status(struct nc_session *session, NC_STATUS status)
162{
163 if (!session) {
romanf578cd52023-10-19 09:47:40 +0200164 ERRARG(session, "session");
Michal Vasko142cfea2017-08-07 10:12:11 +0200165 return;
166 } else if (!status) {
romanf578cd52023-10-19 09:47:40 +0200167 ERRARG(session, "status");
Michal Vasko142cfea2017-08-07 10:12:11 +0200168 return;
169 }
170
171 session->status = status;
172}
173
romanf578cd52023-10-19 09:47:40 +0200174API int
175nc_server_init_ctx(struct ly_ctx **ctx)
176{
177 int new_ctx = 0, i, ret = 0;
178 struct lys_module *module;
179 /* all features */
180 const char *ietf_netconf_features[] = {"writable-running", "candidate", "rollback-on-error", "validate", "startup", "url", "xpath", "confirmed-commit", NULL};
181 /* all features (module has no features) */
182 const char *ietf_netconf_monitoring_features[] = {NULL};
183
184 NC_CHECK_ARG_RET(NULL, ctx, 1);
185
186 if (!*ctx) {
187 /* context not given, create a new one */
188 if (ly_ctx_new(NC_SERVER_SEARCH_DIR, 0, ctx)) {
189 ERR(NULL, "Couldn't create new libyang context.\n");
190 ret = 1;
191 goto cleanup;
192 }
193 new_ctx = 1;
194 }
195
196 if (new_ctx) {
197 /* new context created, implement both modules */
198 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
199 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
200 ret = 1;
201 goto cleanup;
202 }
203
204 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
205 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
206 ret = 1;
207 goto cleanup;
208 }
209
210 goto cleanup;
211 }
212
213 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf");
214 if (module) {
215 /* ietf-netconf module is present, check features */
216 for (i = 0; ietf_netconf_features[i]; i++) {
217 if (lys_feature_value(module, ietf_netconf_features[i])) {
218 /* feature not found, enable all of them */
219 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
220 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
221 ret = 1;
222 goto cleanup;
223 }
224
225 break;
226 }
227 }
228 } else {
229 /* ietf-netconf module not found, add it */
230 if (!ly_ctx_load_module(*ctx, "ietf-netconf", NULL, ietf_netconf_features)) {
231 ERR(NULL, "Loading module \"ietf-netconf\" failed.\n");
232 ret = 1;
233 goto cleanup;
234 }
235 }
236
237 module = ly_ctx_get_module_implemented(*ctx, "ietf-netconf-monitoring");
238 if (!module) {
239 /* ietf-netconf-monitoring module not found, add it */
240 if (!ly_ctx_load_module(*ctx, "ietf-netconf-monitoring", NULL, ietf_netconf_monitoring_features)) {
241 ERR(NULL, "Loading module \"ietf-netconf-monitoring\" failed.\n");
242 ret = 1;
243 goto cleanup;
244 }
245 }
246
247cleanup:
248 if (new_ctx && ret) {
249 ly_ctx_destroy(*ctx);
250 *ctx = NULL;
251 }
252 return ret;
253}
254
roman96c27f92023-11-02 11:09:46 +0100255#ifdef NC_ENABLED_SSH_TLS
256
roman450c00b2023-11-02 10:31:45 +0100257API void
258nc_server_ch_set_dispatch_data(nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
259 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
260 void *new_session_cb_data)
261{
262 NC_CHECK_ARG_RET(NULL, acquire_ctx_cb, release_ctx_cb, new_session_cb, );
263
264 server_opts.ch_dispatch_data.acquire_ctx_cb = acquire_ctx_cb;
265 server_opts.ch_dispatch_data.release_ctx_cb = release_ctx_cb;
266 server_opts.ch_dispatch_data.ctx_cb_data = ctx_cb_data;
267 server_opts.ch_dispatch_data.new_session_cb = new_session_cb;
268 server_opts.ch_dispatch_data.new_session_cb_data = new_session_cb_data;
269}
270
roman96c27f92023-11-02 11:09:46 +0100271#endif
272
Michal Vasko086311b2016-01-08 09:53:11 +0100273int
Michal Vaskoe49a15f2019-05-27 14:18:36 +0200274nc_sock_listen_inet(const char *address, uint16_t port, struct nc_keepalives *ka)
Michal Vasko086311b2016-01-08 09:53:11 +0100275{
Michal Vasko06c860d2018-07-09 16:08:52 +0200276 int opt;
Michal Vasko086311b2016-01-08 09:53:11 +0100277 int is_ipv4, sock;
278 struct sockaddr_storage saddr;
279
280 struct sockaddr_in *saddr4;
281 struct sockaddr_in6 *saddr6;
282
Michal Vasko086311b2016-01-08 09:53:11 +0100283 if (!strchr(address, ':')) {
284 is_ipv4 = 1;
285 } else {
286 is_ipv4 = 0;
287 }
288
289 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
290 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200291 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100292 goto fail;
293 }
294
Michal Vaskobe52dc22018-10-17 09:28:17 +0200295 /* these options will be inherited by accepted sockets */
Michal Vasko06c860d2018-07-09 16:08:52 +0200296 opt = 1;
297 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200298 ERR(NULL, "Could not set SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko06c860d2018-07-09 16:08:52 +0200299 goto fail;
300 }
Michal Vasko83ad17e2019-01-30 10:11:37 +0100301 if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof opt) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200302 ERR(NULL, "Could not set TCP_NODELAY socket option (%s).", strerror(errno));
Michal Vasko83ad17e2019-01-30 10:11:37 +0100303 goto fail;
304 }
Michal Vaskobe52dc22018-10-17 09:28:17 +0200305
romanf578cd52023-10-19 09:47:40 +0200306 if (nc_sock_configure_keepalive(sock, ka)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100307 goto fail;
308 }
309
Michal Vaskof22d5ff2020-04-15 11:10:27 +0200310 memset(&saddr, 0, sizeof(struct sockaddr_storage));
Michal Vasko086311b2016-01-08 09:53:11 +0100311 if (is_ipv4) {
312 saddr4 = (struct sockaddr_in *)&saddr;
313
314 saddr4->sin_family = AF_INET;
315 saddr4->sin_port = htons(port);
316
317 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200318 ERR(NULL, "Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100319 goto fail;
320 }
321
322 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200323 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100324 goto fail;
325 }
326
327 } else {
328 saddr6 = (struct sockaddr_in6 *)&saddr;
329
330 saddr6->sin6_family = AF_INET6;
331 saddr6->sin6_port = htons(port);
332
333 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200334 ERR(NULL, "Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100335 goto fail;
336 }
337
338 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200339 ERR(NULL, "Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100340 goto fail;
341 }
342 }
343
Michal Vaskofb89d772016-01-08 12:25:35 +0100344 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200345 ERR(NULL, "Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100346 goto fail;
347 }
Michal Vasko086311b2016-01-08 09:53:11 +0100348 return sock;
349
350fail:
351 if (sock > -1) {
352 close(sock);
353 }
354
355 return -1;
356}
357
358int
roman83683fb2023-02-24 09:15:23 +0100359nc_sock_listen_unix(const struct nc_server_unix_opts *opts)
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200360{
361 struct sockaddr_un sun;
362 int sock = -1;
363
roman83683fb2023-02-24 09:15:23 +0100364 if (strlen(opts->address) > sizeof(sun.sun_path) - 1) {
365 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 +0200366 goto fail;
367 }
368
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200369 sock = socket(AF_UNIX, SOCK_STREAM, 0);
370 if (sock == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200371 ERR(NULL, "Failed to create socket (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200372 goto fail;
373 }
374
375 memset(&sun, 0, sizeof(sun));
376 sun.sun_family = AF_UNIX;
roman83683fb2023-02-24 09:15:23 +0100377 snprintf(sun.sun_path, sizeof(sun.sun_path) - 1, "%s", opts->address);
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200378
379 unlink(sun.sun_path);
380 if (bind(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
roman83683fb2023-02-24 09:15:23 +0100381 ERR(NULL, "Could not bind \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200382 goto fail;
383 }
384
385 if (opts->mode != (mode_t)-1) {
386 if (chmod(sun.sun_path, opts->mode) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200387 ERR(NULL, "Failed to set unix socket permissions (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200388 goto fail;
389 }
390 }
391
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200392 if ((opts->uid != (uid_t)-1) || (opts->gid != (gid_t)-1)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200393 if (chown(sun.sun_path, opts->uid, opts->gid) < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200394 ERR(NULL, "Failed to set unix socket uid/gid (%s).", strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200395 goto fail;
396 }
397 }
398
399 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
roman83683fb2023-02-24 09:15:23 +0100400 ERR(NULL, "Unable to start listening on \"%s\" (%s).", opts->address, strerror(errno));
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200401 goto fail;
402 }
403
404 return sock;
405
406fail:
407 if (sock > -1) {
408 close(sock);
409 }
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200410 return -1;
411}
412
aPiecek90ff0242021-02-14 14:58:01 +0100413/**
414 * @brief Evaluate socket name for AF_UNIX socket.
415 * @param[in] acc_sock_fd is file descriptor for the accepted socket (a nonnegative).
416 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
417 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
418 * @return 0 if the stream socket is unnamed. Parameter host is set to NULL.
419 * @return -1 in case of error. Parameter host is set to NULL.
420 */
421static int
422sock_host_unix(int acc_sock_fd, char **host)
423{
424 char *sun_path;
425 struct sockaddr_storage saddr;
426 socklen_t addr_len;
427
428 *host = NULL;
429 saddr.ss_family = AF_UNIX;
430 addr_len = sizeof(saddr);
431
432 if (getsockname(acc_sock_fd, (struct sockaddr *)&saddr, &addr_len)) {
Michal Vasko05532772021-06-03 12:12:38 +0200433 ERR(NULL, "getsockname failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100434 return -1;
435 }
436
437 sun_path = ((struct sockaddr_un *)&saddr)->sun_path;
438 if (!sun_path) {
439 /* stream socket is unnamed */
440 return 0;
441 }
442
roman3a95bb22023-10-26 11:07:17 +0200443 NC_CHECK_ERRMEM_RET(!(*host = strdup(sun_path)), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100444
445 return 0;
446}
447
448/**
449 * @brief Evaluate socket name and port number for AF_INET socket.
450 * @param[in] addr is pointing to structure filled by accept function which was successful.
451 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
452 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
453 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
454 * @return -1 in case of error. Parameter host is set to NULL and port is unchanged.
455 */
456static int
457sock_host_inet(const struct sockaddr_in *addr, char **host, uint16_t *port)
458{
459 *host = malloc(INET_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200460 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100461
aPiecek3da9b342021-02-18 15:00:03 +0100462 if (!inet_ntop(AF_INET, &addr->sin_addr, *host, INET_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100463 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100464 free(*host);
465 *host = NULL;
466 return -1;
467 }
468
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200469 *port = ntohs(addr->sin_port);
aPiecek90ff0242021-02-14 14:58:01 +0100470
471 return 0;
472}
473
474/**
475 * @brief Evaluate socket name and port number for AF_INET6 socket.
476 * @param[in] addr is pointing to structure filled by accept function which was successful.
477 * @param[out] host is pointer to char* to which the socket name will be set. It must not be NULL.
478 * @param[out] port is pointer to uint16_t to which the port number will be set. It must not be NULL.
479 * @return 0 in case of success. Call free function for parameter host to avoid a memory leak.
480 * @return -1 in case of error. Parameter host is set to the NULL and port is unchanged.
481 */
482static int
483sock_host_inet6(const struct sockaddr_in6 *addr, char **host, uint16_t *port)
484{
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200485 *host = malloc(INET6_ADDRSTRLEN);
roman3a95bb22023-10-26 11:07:17 +0200486 NC_CHECK_ERRMEM_RET(!(*host), -1);
aPiecek90ff0242021-02-14 14:58:01 +0100487
aPiecek3da9b342021-02-18 15:00:03 +0100488 if (!inet_ntop(AF_INET6, &addr->sin6_addr, *host, INET6_ADDRSTRLEN)) {
Michal Vasko69e98752022-12-14 14:20:17 +0100489 ERR(NULL, "inet_ntop failed (%s).", strerror(errno));
aPiecek90ff0242021-02-14 14:58:01 +0100490 free(*host);
491 *host = NULL;
492 return -1;
493 }
494
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200495 *port = ntohs(addr->sin6_port);
aPiecek90ff0242021-02-14 14:58:01 +0100496
497 return 0;
498}
499
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200500int
Michal Vasko6f865982023-11-21 12:10:42 +0100501nc_sock_accept_binds(struct nc_bind *binds, uint16_t bind_count, pthread_mutex_t *bind_lock, int timeout, char **host,
502 uint16_t *port, uint16_t *idx)
Michal Vasko086311b2016-01-08 09:53:11 +0100503{
Michal Vaskof54cd352017-02-22 13:42:02 +0100504 sigset_t sigmask, origmask;
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200505 uint16_t i, j, pfd_count, client_port;
506 char *client_address;
Michal Vasko086311b2016-01-08 09:53:11 +0100507 struct pollfd *pfd;
508 struct sockaddr_storage saddr;
509 socklen_t saddr_len = sizeof(saddr);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200510 int ret, client_sock, sock = -1, flags;
Michal Vasko086311b2016-01-08 09:53:11 +0100511
512 pfd = malloc(bind_count * sizeof *pfd);
roman3a95bb22023-10-26 11:07:17 +0200513 NC_CHECK_ERRMEM_RET(!pfd, -1);
Michal Vasko4eb3c312016-03-01 14:09:37 +0100514
romanf578cd52023-10-19 09:47:40 +0200515 /* LOCK */
516 pthread_mutex_lock(bind_lock);
517
Michal Vaskoac2f6182017-01-30 14:32:03 +0100518 for (i = 0, pfd_count = 0; i < bind_count; ++i) {
Michal Vasko94acafc2016-09-23 13:40:10 +0200519 if (binds[i].sock < 0) {
520 /* invalid socket */
Michal Vasko94acafc2016-09-23 13:40:10 +0200521 continue;
522 }
Michal Vasko0a3f3752016-10-13 14:58:38 +0200523 if (binds[i].pollin) {
524 binds[i].pollin = 0;
525 /* leftover pollin */
526 sock = binds[i].sock;
Michal Vasko086311b2016-01-08 09:53:11 +0100527 break;
528 }
Michal Vaskoac2f6182017-01-30 14:32:03 +0100529 pfd[pfd_count].fd = binds[i].sock;
530 pfd[pfd_count].events = POLLIN;
531 pfd[pfd_count].revents = 0;
532
533 ++pfd_count;
Michal Vasko086311b2016-01-08 09:53:11 +0100534 }
535
Michal Vasko0a3f3752016-10-13 14:58:38 +0200536 if (sock == -1) {
537 /* poll for a new connection */
Michal Vaskof54cd352017-02-22 13:42:02 +0100538 sigfillset(&sigmask);
539 pthread_sigmask(SIG_SETMASK, &sigmask, &origmask);
Michal Vaskoac2f6182017-01-30 14:32:03 +0100540 ret = poll(pfd, pfd_count, timeout);
Michal Vaskof54cd352017-02-22 13:42:02 +0100541 pthread_sigmask(SIG_SETMASK, &origmask, NULL);
542
Michal Vasko0a3f3752016-10-13 14:58:38 +0200543 if (!ret) {
544 /* we timeouted */
545 free(pfd);
romanf578cd52023-10-19 09:47:40 +0200546 /* UNLOCK */
547 pthread_mutex_unlock(bind_lock);
Michal Vasko0a3f3752016-10-13 14:58:38 +0200548 return 0;
549 } else if (ret == -1) {
Michal Vasko05532772021-06-03 12:12:38 +0200550 ERR(NULL, "Poll failed (%s).", strerror(errno));
Michal Vasko0a3f3752016-10-13 14:58:38 +0200551 free(pfd);
romanf578cd52023-10-19 09:47:40 +0200552 /* UNLOCK */
553 pthread_mutex_unlock(bind_lock);
Michal Vasko0a3f3752016-10-13 14:58:38 +0200554 return -1;
555 }
Michal Vasko086311b2016-01-08 09:53:11 +0100556
Michal Vaskoac2f6182017-01-30 14:32:03 +0100557 for (i = 0, j = 0; j < pfd_count; ++i, ++j) {
558 /* adjust i so that indices in binds and pfd always match */
559 while (binds[i].sock != pfd[j].fd) {
560 ++i;
561 }
562
563 if (pfd[j].revents & POLLIN) {
Michal Vasko0a3f3752016-10-13 14:58:38 +0200564 --ret;
565
566 if (!ret) {
567 /* the last socket with an event, use it */
Michal Vaskoac2f6182017-01-30 14:32:03 +0100568 sock = pfd[j].fd;
Michal Vasko0a3f3752016-10-13 14:58:38 +0200569 break;
570 } else {
571 /* just remember the event for next time */
572 binds[i].pollin = 1;
573 }
574 }
Michal Vasko086311b2016-01-08 09:53:11 +0100575 }
576 }
577 free(pfd);
Michal Vasko086311b2016-01-08 09:53:11 +0100578 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100579 ERRINT;
romanf578cd52023-10-19 09:47:40 +0200580 /* UNLOCK */
581 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100582 return -1;
583 }
584
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200585 /* accept connection */
586 client_sock = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
587 if (client_sock < 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200588 ERR(NULL, "Accept failed (%s).", strerror(errno));
romanf578cd52023-10-19 09:47:40 +0200589 /* UNLOCK */
590 pthread_mutex_unlock(bind_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100591 return -1;
592 }
593
Michal Vasko0190bc32016-03-02 15:47:49 +0100594 /* make the socket non-blocking */
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200595 if (((flags = fcntl(client_sock, F_GETFL)) == -1) || (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200596 ERR(NULL, "Fcntl failed (%s).", strerror(errno));
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200597 goto fail;
Michal Vasko0190bc32016-03-02 15:47:49 +0100598 }
599
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200600 /* learn information about the client end */
601 if (saddr.ss_family == AF_UNIX) {
602 if (sock_host_unix(client_sock, &client_address)) {
603 goto fail;
604 }
605 client_port = 0;
606 } else if (saddr.ss_family == AF_INET) {
607 if (sock_host_inet((struct sockaddr_in *)&saddr, &client_address, &client_port)) {
608 goto fail;
609 }
610 } else if (saddr.ss_family == AF_INET6) {
611 if (sock_host_inet6((struct sockaddr_in6 *)&saddr, &client_address, &client_port)) {
612 goto fail;
613 }
614 } else {
615 ERR(NULL, "Source host of an unknown protocol family.");
616 goto fail;
aPiecek90ff0242021-02-14 14:58:01 +0100617 }
Michal Vasko086311b2016-01-08 09:53:11 +0100618
aPiecek90ff0242021-02-14 14:58:01 +0100619 if (saddr.ss_family == AF_UNIX) {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200620 VRB(NULL, "Accepted a connection on %s.", binds[i].address);
aPiecek90ff0242021-02-14 14:58:01 +0100621 } else {
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200622 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 +0100623 }
624
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200625 if (host) {
626 *host = client_address;
627 } else {
628 free(client_address);
629 }
630 if (port) {
631 *port = client_port;
632 }
633 if (idx) {
634 *idx = i;
635 }
romanf578cd52023-10-19 09:47:40 +0200636 /* UNLOCK */
637 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200638 return client_sock;
639
640fail:
641 close(client_sock);
romanf578cd52023-10-19 09:47:40 +0200642 /* UNLOCK */
643 pthread_mutex_unlock(bind_lock);
Michal Vasko89ffa8a2021-06-25 08:40:08 +0200644 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100645}
646
Michal Vasko238b6c12021-12-14 15:14:09 +0100647API struct nc_server_reply *
Michal Vasko05532772021-06-03 12:12:38 +0200648nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100649{
Michal Vasko77367452021-02-16 16:32:18 +0100650 const char *identifier = NULL, *revision = NULL, *format = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100651 char *model_data = NULL;
Michal Vasko77367452021-02-16 16:32:18 +0100652 struct ly_out *out;
Michal Vasko9b1a9522021-03-15 16:24:26 +0100653 const struct lys_module *module = NULL, *mod;
Michal Vasko77367452021-02-16 16:32:18 +0100654 const struct lysp_submodule *submodule = NULL;
655 struct lyd_node *child, *err, *data = NULL;
656 LYS_OUTFORMAT outformat = 0;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100657
Michal Vasko77367452021-02-16 16:32:18 +0100658 LY_LIST_FOR(lyd_child(rpc), child) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100659 if (!strcmp(child->schema->name, "identifier")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200660 identifier = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100661 } else if (!strcmp(child->schema->name, "version")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200662 revision = lyd_get_value(child);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200663 if (revision && (revision[0] == '\0')) {
Michal Vasko77367452021-02-16 16:32:18 +0100664 revision = NULL;
Radek Krejci1afa7792017-03-26 11:24:16 -0500665 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100666 } else if (!strcmp(child->schema->name, "format")) {
Michal Vaskoe97b10a2021-04-28 08:52:52 +0200667 format = lyd_get_value(child);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100668 }
669 }
Michal Vasko5ca5d972022-09-14 13:51:31 +0200670 VRB(session, "Module \"%s@%s\" was requested.", identifier, revision ? revision : "<any>");
Michal Vasko05ba9df2016-01-13 14:40:27 +0100671
Michal Vasko77367452021-02-16 16:32:18 +0100672 /* check revision */
673 if (revision && (strlen(revision) != 10) && strcmp(revision, "1.0")) {
Michal Vasko93224072021-11-09 12:14:28 +0100674 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100675 nc_err_set_msg(err, "The requested version is not supported.", "en");
676 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100677 }
678
Michal Vasko77367452021-02-16 16:32:18 +0100679 if (revision) {
680 /* get specific module */
Michal Vasko93224072021-11-09 12:14:28 +0100681 module = ly_ctx_get_module(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100682 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100683 submodule = ly_ctx_get_submodule(session->ctx, identifier, revision);
Michal Vasko77367452021-02-16 16:32:18 +0100684 }
685 } else {
686 /* try to get implemented, then latest module */
Michal Vasko93224072021-11-09 12:14:28 +0100687 module = ly_ctx_get_module_implemented(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100688 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100689 module = ly_ctx_get_module_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100690 }
691 if (!module) {
Michal Vasko93224072021-11-09 12:14:28 +0100692 submodule = ly_ctx_get_submodule_latest(session->ctx, identifier);
Michal Vasko77367452021-02-16 16:32:18 +0100693 }
Michal Vaskod91f6e62016-04-05 11:34:22 +0200694 }
Michal Vasko77367452021-02-16 16:32:18 +0100695 if (!module && !submodule) {
Michal Vasko93224072021-11-09 12:14:28 +0100696 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko5ca5d972022-09-14 13:51:31 +0200697 nc_err_set_msg(err, "The requested module was not found.", "en");
Michal Vasko1a38c862016-01-15 15:50:07 +0100698 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100699 }
700
701 /* check format */
Radek Krejci90fba642016-12-07 15:59:45 +0100702 if (!format || !strcmp(format, "ietf-netconf-monitoring:yang")) {
Michal Vasko77367452021-02-16 16:32:18 +0100703 outformat = LYS_OUT_YANG;
Radek Krejci90fba642016-12-07 15:59:45 +0100704 } else if (!strcmp(format, "ietf-netconf-monitoring:yin")) {
Michal Vasko77367452021-02-16 16:32:18 +0100705 outformat = LYS_OUT_YIN;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100706 } else {
Michal Vasko93224072021-11-09 12:14:28 +0100707 err = nc_err(session->ctx, NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
Michal Vasko1a38c862016-01-15 15:50:07 +0100708 nc_err_set_msg(err, "The requested format is not supported.", "en");
709 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100710 }
Michal Vasko77367452021-02-16 16:32:18 +0100711
712 /* print */
713 ly_out_new_memory(&model_data, 0, &out);
714 if (module) {
715 lys_print_module(out, module, outformat, 0, 0);
716 } else {
717 lys_print_submodule(out, submodule, outformat, 0, 0);
718 }
719 ly_out_free(out, NULL, 0);
Michal Vaskod91f6e62016-04-05 11:34:22 +0200720 if (!model_data) {
721 ERRINT;
722 return NULL;
723 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100724
Michal Vasko9b1a9522021-03-15 16:24:26 +0100725 /* create reply */
Michal Vasko93224072021-11-09 12:14:28 +0100726 mod = ly_ctx_get_module_implemented(session->ctx, "ietf-netconf-monitoring");
Michal Vasko9b1a9522021-03-15 16:24:26 +0100727 if (!mod || lyd_new_inner(NULL, mod, "get-schema", 0, &data)) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100728 ERRINT;
Michal Vaskod91f6e62016-04-05 11:34:22 +0200729 free(model_data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100730 return NULL;
731 }
Michal Vasko9b1a9522021-03-15 16:24:26 +0100732 if (lyd_new_any(data, NULL, "data", model_data, 1, LYD_ANYDATA_STRING, 1, NULL)) {
733 ERRINT;
Michal Vaskoa50f68e2022-02-24 16:10:54 +0100734 free(model_data);
Michal Vasko9b1a9522021-03-15 16:24:26 +0100735 lyd_free_tree(data);
736 return NULL;
737 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100738
Radek Krejci36dfdb32016-09-01 16:56:35 +0200739 return nc_server_reply_data(data, NC_WD_EXPLICIT, NC_PARAMTYPE_FREE);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100740}
741
Michal Vasko238b6c12021-12-14 15:14:09 +0100742API struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100743nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100744{
Michal Vasko428087d2016-01-14 16:04:28 +0100745 session->term_reason = NC_SESSION_TERM_CLOSED;
746 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100747}
748
Michal Vasko93224072021-11-09 12:14:28 +0100749/**
750 * @brief Initialize a context with default RPC callbacks if none are set.
751 *
752 * @param[in] ctx Context to initialize.
753 */
754static void
romanf578cd52023-10-19 09:47:40 +0200755nc_server_init_cb_ctx(const struct ly_ctx *ctx)
Michal Vasko086311b2016-01-08 09:53:11 +0100756{
Michal Vasko77367452021-02-16 16:32:18 +0100757 struct lysc_node *rpc;
Michal Vaskoa7b8ca52016-03-01 12:09:29 +0100758
Michal Vasko238b6c12021-12-14 15:14:09 +0100759 if (global_rpc_clb) {
760 /* expect it to handle these RPCs as well */
761 return;
762 }
763
Michal Vasko05ba9df2016-01-13 14:40:27 +0100764 /* set default <get-schema> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100765 rpc = NULL;
766 if (ly_ctx_get_module_implemented(ctx, "ietf-netconf-monitoring")) {
767 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf-monitoring:get-schema", 0);
768 }
Michal Vasko88639e92017-08-03 14:38:10 +0200769 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100770 rpc->priv = nc_clb_default_get_schema;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100771 }
772
Michal Vasko93224072021-11-09 12:14:28 +0100773 /* set default <close-session> callback if not specified */
Michal Vasko77367452021-02-16 16:32:18 +0100774 rpc = (struct lysc_node *)lys_find_path(ctx, NULL, "/ietf-netconf:close-session", 0);
Michal Vasko88639e92017-08-03 14:38:10 +0200775 if (rpc && !rpc->priv) {
Michal Vasko77367452021-02-16 16:32:18 +0100776 rpc->priv = nc_clb_default_close_session;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100777 }
Michal Vasko93224072021-11-09 12:14:28 +0100778}
Michal Vasko05ba9df2016-01-13 14:40:27 +0100779
Michal Vasko93224072021-11-09 12:14:28 +0100780API int
781nc_server_init(void)
782{
783 pthread_rwlockattr_t attr, *attr_p = NULL;
784 int r;
785
Michal Vaskob48aa812016-01-18 14:13:09 +0100786 server_opts.new_session_id = 1;
Andrew Langefeld6ed922d2018-09-12 14:08:32 -0500787 server_opts.new_client_id = 1;
Michal Vaskob48aa812016-01-18 14:13:09 +0100788
Michal Vasko93224072021-11-09 12:14:28 +0100789#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
790 if ((r = pthread_rwlockattr_init(&attr))) {
791 ERR(NULL, "%s: failed init attribute (%s).", __func__, strerror(r));
792 goto error;
793 }
794 attr_p = &attr;
795 if ((r = pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP))) {
796 ERR(NULL, "%s: failed set attribute (%s).", __func__, strerror(r));
797 goto error;
798 }
Rosen Penevef2f3ac2019-07-15 18:15:28 -0700799#endif
Michal Vasko93224072021-11-09 12:14:28 +0100800
romanf578cd52023-10-19 09:47:40 +0200801 if ((r = pthread_rwlock_init(&server_opts.config_lock, attr_p))) {
Michal Vasko93224072021-11-09 12:14:28 +0100802 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
803 goto error;
804 }
805 if ((r = pthread_rwlock_init(&server_opts.ch_client_lock, attr_p))) {
806 ERR(NULL, "%s: failed to init rwlock(%s).", __func__, strerror(r));
807 goto error;
808 }
809
810 if (attr_p) {
811 pthread_rwlockattr_destroy(attr_p);
Frank Rimpler9f838b02018-07-25 06:44:03 +0000812 }
romanf578cd52023-10-19 09:47:40 +0200813
814#ifdef NC_ENABLED_SSH_TLS
815 if (curl_global_init(CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR)) {
816 ERR(NULL, "%s: failed to init CURL.", __func__);
817 goto error;
818 }
819#endif
820
821 if ((r = pthread_mutex_init(&server_opts.bind_lock, NULL))) {
822 ERR(NULL, "%s: failed to init bind lock(%s).", __func__, strerror(r));
823 goto error;
824 }
825
Michal Vasko086311b2016-01-08 09:53:11 +0100826 return 0;
Michal Vasko93224072021-11-09 12:14:28 +0100827
828error:
829 if (attr_p) {
830 pthread_rwlockattr_destroy(attr_p);
831 }
832 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100833}
834
Michal Vaskob48aa812016-01-18 14:13:09 +0100835API void
836nc_server_destroy(void)
837{
Michal Vasko1440a742021-03-31 11:11:03 +0200838 uint32_t i;
Radek Krejci658782b2016-12-04 22:04:55 +0100839
840 for (i = 0; i < server_opts.capabilities_count; i++) {
Michal Vasko93224072021-11-09 12:14:28 +0100841 free(server_opts.capabilities[i]);
Radek Krejci658782b2016-12-04 22:04:55 +0100842 }
843 free(server_opts.capabilities);
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200844 server_opts.capabilities = NULL;
845 server_opts.capabilities_count = 0;
Michal Vasko1440a742021-03-31 11:11:03 +0200846 if (server_opts.content_id_data && server_opts.content_id_data_free) {
847 server_opts.content_id_data_free(server_opts.content_id_data);
848 }
Michal Vaskodd6e4f72018-06-01 10:21:27 +0200849
romanf578cd52023-10-19 09:47:40 +0200850 nc_server_config_listen(NULL, NC_OP_DELETE);
851 nc_server_config_ch(NULL, NC_OP_DELETE);
852
853 pthread_mutex_destroy(&server_opts.bind_lock);
854
855#ifdef NC_ENABLED_SSH_TLS
roman808f3f62023-11-23 16:01:04 +0100856 free(server_opts.pam_config_name);
857 server_opts.pam_config_name = NULL;
Michal Vasko1c2d2652023-10-17 08:53:36 +0200858 if (server_opts.interactive_auth_data && server_opts.interactive_auth_data_free) {
859 server_opts.interactive_auth_data_free(server_opts.interactive_auth_data);
860 }
861 server_opts.interactive_auth_data = NULL;
862 server_opts.interactive_auth_data_free = NULL;
863
romanf578cd52023-10-19 09:47:40 +0200864 nc_server_config_ks_keystore(NULL, NC_OP_DELETE);
865 nc_server_config_ts_truststore(NULL, NC_OP_DELETE);
866 curl_global_cleanup();
867#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob48aa812016-01-18 14:13:09 +0100868}
869
Michal Vasko086311b2016-01-08 09:53:11 +0100870API int
871nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
872{
Michal Vasko45e53ae2016-04-07 11:46:03 +0200873 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)) {
romanf578cd52023-10-19 09:47:40 +0200874 ERRARG(NULL, "basic_mode");
Michal Vasko45e53ae2016-04-07 11:46:03 +0200875 return -1;
876 } 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 +0200877 ERRARG(NULL, "also_supported");
Michal Vasko086311b2016-01-08 09:53:11 +0100878 return -1;
879 }
880
romanf578cd52023-10-19 09:47:40 +0200881 ATOMIC_STORE_RELAXED(server_opts.wd_basic_mode, basic_mode);
882 ATOMIC_STORE_RELAXED(server_opts.wd_also_supported, also_supported);
Michal Vasko086311b2016-01-08 09:53:11 +0100883 return 0;
884}
885
Michal Vasko1a38c862016-01-15 15:50:07 +0100886API void
Michal Vasko55f03972016-04-13 08:56:01 +0200887nc_server_get_capab_withdefaults(NC_WD_MODE *basic_mode, int *also_supported)
888{
889 if (!basic_mode && !also_supported) {
romanf578cd52023-10-19 09:47:40 +0200890 ERRARG(NULL, "basic_mode and also_supported");
Michal Vasko55f03972016-04-13 08:56:01 +0200891 return;
892 }
893
894 if (basic_mode) {
romanf578cd52023-10-19 09:47:40 +0200895 *basic_mode = ATOMIC_LOAD_RELAXED(server_opts.wd_basic_mode);
Michal Vasko55f03972016-04-13 08:56:01 +0200896 }
897 if (also_supported) {
romanf578cd52023-10-19 09:47:40 +0200898 *also_supported = ATOMIC_LOAD_RELAXED(server_opts.wd_also_supported);
Michal Vasko55f03972016-04-13 08:56:01 +0200899 }
900}
901
Michal Vasko55f03972016-04-13 08:56:01 +0200902API int
Radek Krejci658782b2016-12-04 22:04:55 +0100903nc_server_set_capability(const char *value)
Michal Vasko55f03972016-04-13 08:56:01 +0200904{
Michal Vasko93224072021-11-09 12:14:28 +0100905 void *mem;
Radek Krejci658782b2016-12-04 22:04:55 +0100906
907 if (!value || !value[0]) {
romanf578cd52023-10-19 09:47:40 +0200908 ERRARG(NULL, "value must not be empty");
Radek Krejci658782b2016-12-04 22:04:55 +0100909 return EXIT_FAILURE;
910 }
911
Michal Vasko93224072021-11-09 12:14:28 +0100912 mem = realloc(server_opts.capabilities, (server_opts.capabilities_count + 1) * sizeof *server_opts.capabilities);
roman3a95bb22023-10-26 11:07:17 +0200913 NC_CHECK_ERRMEM_RET(!mem, EXIT_FAILURE);
Michal Vasko93224072021-11-09 12:14:28 +0100914 server_opts.capabilities = mem;
915
916 server_opts.capabilities[server_opts.capabilities_count] = strdup(value);
917 server_opts.capabilities_count++;
Radek Krejci658782b2016-12-04 22:04:55 +0100918
919 return EXIT_SUCCESS;
Michal Vasko55f03972016-04-13 08:56:01 +0200920}
921
Michal Vasko1a38c862016-01-15 15:50:07 +0100922API void
Michal Vasko1440a742021-03-31 11:11:03 +0200923nc_server_set_content_id_clb(char *(*content_id_clb)(void *user_data), void *user_data,
924 void (*free_user_data)(void *user_data))
925{
926 server_opts.content_id_clb = content_id_clb;
927 server_opts.content_id_data = user_data;
928 server_opts.content_id_data_free = free_user_data;
929}
930
Michal Vasko71090fc2016-05-24 16:37:28 +0200931API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +0100932nc_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 +0100933{
Michal Vasko71090fc2016-05-24 16:37:28 +0200934 NC_MSG_TYPE msgtype;
Michal Vasko9fb42272017-10-05 13:50:05 +0200935 struct timespec ts_cur;
Michal Vasko71090fc2016-05-24 16:37:28 +0200936
romanf578cd52023-10-19 09:47:40 +0200937 NC_CHECK_ARG_RET(NULL, ctx, username, session, NC_MSG_ERROR);
938
939 if (fdin < 0) {
940 ERRARG(NULL, "fdin");
Michal Vasko71090fc2016-05-24 16:37:28 +0200941 return NC_MSG_ERROR;
Michal Vasko45e53ae2016-04-07 11:46:03 +0200942 } else if (fdout < 0) {
romanf578cd52023-10-19 09:47:40 +0200943 ERRARG(NULL, "fdout");
Michal Vasko71090fc2016-05-24 16:37:28 +0200944 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +0100945 }
946
Michal Vasko93224072021-11-09 12:14:28 +0100947 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +0200948 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +0100949
Michal Vasko086311b2016-01-08 09:53:11 +0100950 /* prepare session structure */
Michal Vasko131120a2018-05-29 15:44:02 +0200951 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +0200952 NC_CHECK_ERRMEM_RET(!(*session), NC_MSG_ERROR);
Michal Vasko1a38c862016-01-15 15:50:07 +0100953 (*session)->status = NC_STATUS_STARTING;
Michal Vaskoade892d2017-02-22 13:40:35 +0100954
Michal Vasko086311b2016-01-08 09:53:11 +0100955 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100956 (*session)->ti_type = NC_TI_FD;
957 (*session)->ti.fd.in = fdin;
958 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100959
Michal Vasko93224072021-11-09 12:14:28 +0100960 /* assign context */
Michal Vasko1a38c862016-01-15 15:50:07 +0100961 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +0100962 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100963
Michal Vaskob48aa812016-01-18 14:13:09 +0100964 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +0200965 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +0100966
Michal Vasko086311b2016-01-08 09:53:11 +0100967 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +0200968 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +0200969 if (msgtype != NC_MSG_HELLO) {
970 nc_session_free(*session, NULL);
971 *session = NULL;
972 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100973 }
Michal Vasko9fb42272017-10-05 13:50:05 +0200974
Michal Vaskod8a74192023-02-06 15:51:50 +0100975 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +0200976 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +0100977 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +0100978 (*session)->opts.server.session_start = ts_cur;
Michal Vasko9fb42272017-10-05 13:50:05 +0200979
Michal Vasko1a38c862016-01-15 15:50:07 +0100980 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100981
Michal Vasko71090fc2016-05-24 16:37:28 +0200982 return msgtype;
Michal Vasko086311b2016-01-08 09:53:11 +0100983}
Michal Vasko9e036d52016-01-08 10:49:26 +0100984
Michal Vaskob30b99c2016-07-26 11:35:43 +0200985static void
Michal Vasko74c345f2018-02-07 10:37:11 +0100986nc_ps_queue_add_id(struct nc_pollsession *ps, uint8_t *id)
987{
988 uint8_t q_last;
989
990 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
991 ERRINT;
992 return;
993 }
994
995 /* get a unique queue value (by adding 1 to the last added value, if any) */
996 if (ps->queue_len) {
997 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
998 *id = ps->queue[q_last] + 1;
999 } else {
1000 *id = 0;
1001 }
1002
1003 /* add the id into the queue */
1004 ++ps->queue_len;
1005 q_last = (ps->queue_begin + ps->queue_len - 1) % NC_PS_QUEUE_SIZE;
1006 ps->queue[q_last] = *id;
1007}
1008
1009static void
Michal Vaskob30b99c2016-07-26 11:35:43 +02001010nc_ps_queue_remove_id(struct nc_pollsession *ps, uint8_t id)
1011{
Michal Vasko74c345f2018-02-07 10:37:11 +01001012 uint8_t i, q_idx, found = 0;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001013
1014 for (i = 0; i < ps->queue_len; ++i) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001015 /* get the actual queue idx */
1016 q_idx = (ps->queue_begin + i) % NC_PS_QUEUE_SIZE;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001017
1018 if (found) {
Michal Vasko74c345f2018-02-07 10:37:11 +01001019 if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001020 /* another equal value, simply cannot be */
1021 ERRINT;
1022 }
Michal Vaskod8340032018-02-12 14:41:00 +01001023 if (found == 2) {
1024 /* move the following values */
1025 ps->queue[q_idx ? q_idx - 1 : NC_PS_QUEUE_SIZE - 1] = ps->queue[q_idx];
1026 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001027 } else if (ps->queue[q_idx] == id) {
Michal Vaskob30b99c2016-07-26 11:35:43 +02001028 /* found our id, there can be no more equal valid values */
Michal Vaskod8340032018-02-12 14:41:00 +01001029 if (i == 0) {
1030 found = 1;
1031 } else {
1032 /* this is not okay, our id is in the middle of the queue */
1033 found = 2;
1034 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001035 }
1036 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001037 if (!found) {
1038 ERRINT;
Michal Vasko103fe632018-02-12 16:37:45 +01001039 return;
Michal Vaskob30b99c2016-07-26 11:35:43 +02001040 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001041
Michal Vasko103fe632018-02-12 16:37:45 +01001042 --ps->queue_len;
Michal Vaskod8340032018-02-12 14:41:00 +01001043 if (found == 1) {
Michal Vasko103fe632018-02-12 16:37:45 +01001044 /* remove the id by moving the queue, otherwise all the values in the queue were moved */
Michal Vaskod8340032018-02-12 14:41:00 +01001045 ps->queue_begin = (ps->queue_begin + 1) % NC_PS_QUEUE_SIZE;
1046 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001047}
1048
Michal Vaskof04a52a2016-04-07 10:52:10 +02001049int
Michal Vasko26043172016-07-26 14:08:59 +02001050nc_ps_lock(struct nc_pollsession *ps, uint8_t *id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001051{
1052 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001053 struct timespec ts;
1054
Michal Vaskobe86fe32016-04-07 10:43:03 +02001055 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001056 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001057 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001058 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001059 return -1;
1060 }
1061
Michal Vasko74c345f2018-02-07 10:37:11 +01001062 /* check that the queue is long enough */
Michal Vasko8bc747c2018-02-09 16:37:04 +01001063 if (ps->queue_len == NC_PS_QUEUE_SIZE) {
Michal Vasko05532772021-06-03 12:12:38 +02001064 ERR(NULL, "%s: pollsession queue size (%d) too small.", func, NC_PS_QUEUE_SIZE);
Michal Vasko8bc747c2018-02-09 16:37:04 +01001065 pthread_mutex_unlock(&ps->lock);
1066 return -1;
1067 }
Michal Vasko74c345f2018-02-07 10:37:11 +01001068
1069 /* add ourselves into the queue */
1070 nc_ps_queue_add_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001071 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 +02001072 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001073
1074 /* is it our turn? */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001075 while (ps->queue[ps->queue_begin] != *id) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001076 nc_timeouttime_get(&ts, NC_PS_QUEUE_TIMEOUT);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001077
Michal Vaskod8a74192023-02-06 15:51:50 +01001078 ret = pthread_cond_clockwait(&ps->cond, &ps->lock, COMPAT_CLOCK_ID, &ts);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001079 if (ret) {
preetbhansalif3edf492018-12-14 17:53:32 +05301080 /**
1081 * This may happen when another thread releases the lock and broadcasts the condition
1082 * and this thread had already timed out. When this thread is scheduled, it returns timed out error
1083 * but when actually this thread was ready for condition.
1084 */
preetbhansali629dfc42018-12-17 16:04:40 +05301085 if ((ETIMEDOUT == ret) && (ps->queue[ps->queue_begin] == *id)) {
preetbhansalif3edf492018-12-14 17:53:32 +05301086 break;
1087 }
Michal Vasko66032bc2019-01-22 15:03:12 +01001088
Michal Vasko05532772021-06-03 12:12:38 +02001089 ERR(NULL, "%s: failed to wait for a pollsession condition (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001090 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001091 nc_ps_queue_remove_id(ps, *id);
1092 pthread_mutex_unlock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001093 return -1;
1094 }
1095 }
1096
Michal Vaskobe86fe32016-04-07 10:43:03 +02001097 /* UNLOCK */
1098 pthread_mutex_unlock(&ps->lock);
1099
1100 return 0;
1101}
1102
Michal Vaskof04a52a2016-04-07 10:52:10 +02001103int
Michal Vasko26043172016-07-26 14:08:59 +02001104nc_ps_unlock(struct nc_pollsession *ps, uint8_t id, const char *func)
Michal Vaskobe86fe32016-04-07 10:43:03 +02001105{
1106 int ret;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001107
1108 /* LOCK */
Michal Vasko8c7def52023-06-06 14:48:56 +02001109 ret = pthread_mutex_lock(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001110 if (ret) {
Michal Vasko05532772021-06-03 12:12:38 +02001111 ERR(NULL, "%s: failed to lock a pollsession (%s).", func, strerror(ret));
Michal Vaskobe86fe32016-04-07 10:43:03 +02001112 ret = -1;
1113 }
1114
Michal Vaskob30b99c2016-07-26 11:35:43 +02001115 /* we must be the first, it was our turn after all, right? */
1116 if (ps->queue[ps->queue_begin] != id) {
1117 ERRINT;
Michal Vaskob1a094b2016-10-05 14:04:52 +02001118 /* UNLOCK */
1119 if (!ret) {
1120 pthread_mutex_unlock(&ps->lock);
1121 }
Michal Vaskob30b99c2016-07-26 11:35:43 +02001122 return -1;
1123 }
1124
Michal Vaskobe86fe32016-04-07 10:43:03 +02001125 /* remove ourselves from the queue */
Michal Vaskob30b99c2016-07-26 11:35:43 +02001126 nc_ps_queue_remove_id(ps, id);
Michal Vaskofdba4a32022-01-05 12:13:53 +01001127 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 +02001128 ps->queue[ps->queue_begin], ps->queue_len);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001129
1130 /* broadcast to all other threads that the queue moved */
1131 pthread_cond_broadcast(&ps->cond);
1132
Michal Vaskobe86fe32016-04-07 10:43:03 +02001133 /* UNLOCK */
1134 if (!ret) {
1135 pthread_mutex_unlock(&ps->lock);
1136 }
1137
1138 return ret;
1139}
1140
Michal Vasko428087d2016-01-14 16:04:28 +01001141API struct nc_pollsession *
1142nc_ps_new(void)
1143{
Michal Vasko48a63ed2016-03-01 09:48:21 +01001144 struct nc_pollsession *ps;
1145
1146 ps = calloc(1, sizeof(struct nc_pollsession));
roman3a95bb22023-10-26 11:07:17 +02001147 NC_CHECK_ERRMEM_RET(!ps, NULL);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001148 pthread_cond_init(&ps->cond, NULL);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001149 pthread_mutex_init(&ps->lock, NULL);
1150
1151 return ps;
Michal Vasko428087d2016-01-14 16:04:28 +01001152}
1153
1154API void
1155nc_ps_free(struct nc_pollsession *ps)
1156{
fanchanghu3d4e7212017-08-09 09:42:30 +08001157 uint16_t i;
1158
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001159 if (!ps) {
1160 return;
1161 }
1162
Michal Vaskobe86fe32016-04-07 10:43:03 +02001163 if (ps->queue_len) {
Michal Vasko05532772021-06-03 12:12:38 +02001164 ERR(NULL, "FATAL: Freeing a pollsession structure that is currently being worked with!");
Michal Vaskobe86fe32016-04-07 10:43:03 +02001165 }
1166
fanchanghu3d4e7212017-08-09 09:42:30 +08001167 for (i = 0; i < ps->session_count; i++) {
1168 free(ps->sessions[i]);
1169 }
1170
Michal Vasko428087d2016-01-14 16:04:28 +01001171 free(ps->sessions);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001172 pthread_mutex_destroy(&ps->lock);
Michal Vaskobe86fe32016-04-07 10:43:03 +02001173 pthread_cond_destroy(&ps->cond);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001174
Michal Vasko428087d2016-01-14 16:04:28 +01001175 free(ps);
1176}
1177
1178API int
1179nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
1180{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001181 uint8_t q_id;
1182
romanf578cd52023-10-19 09:47:40 +02001183 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko428087d2016-01-14 16:04:28 +01001184
Michal Vasko48a63ed2016-03-01 09:48:21 +01001185 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001186 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001187 return -1;
1188 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001189
Michal Vasko428087d2016-01-14 16:04:28 +01001190 ++ps->session_count;
Michal Vasko4eb3c312016-03-01 14:09:37 +01001191 ps->sessions = nc_realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
Michal Vasko9a327362017-01-11 11:31:46 +01001192 if (!ps->sessions) {
Michal Vasko4eb3c312016-03-01 14:09:37 +01001193 ERRMEM;
1194 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001195 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko4eb3c312016-03-01 14:09:37 +01001196 return -1;
1197 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001198 ps->sessions[ps->session_count - 1] = calloc(1, sizeof **ps->sessions);
1199 if (!ps->sessions[ps->session_count - 1]) {
1200 ERRMEM;
1201 --ps->session_count;
1202 /* UNLOCK */
1203 nc_ps_unlock(ps, q_id, __func__);
1204 return -1;
1205 }
1206 ps->sessions[ps->session_count - 1]->session = session;
1207 ps->sessions[ps->session_count - 1]->state = NC_PS_STATE_NONE;
Michal Vasko428087d2016-01-14 16:04:28 +01001208
Michal Vasko48a63ed2016-03-01 09:48:21 +01001209 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001210 return nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001211}
1212
Michal Vasko48a63ed2016-03-01 09:48:21 +01001213static int
Radek Krejcid5f978f2016-03-03 13:14:45 +01001214_nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session, int index)
Michal Vasko428087d2016-01-14 16:04:28 +01001215{
1216 uint16_t i;
1217
Radek Krejcid5f978f2016-03-03 13:14:45 +01001218 if (index >= 0) {
1219 i = (uint16_t)index;
1220 goto remove;
1221 }
Michal Vasko428087d2016-01-14 16:04:28 +01001222 for (i = 0; i < ps->session_count; ++i) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001223 if (ps->sessions[i]->session == session) {
Radek Krejcid5f978f2016-03-03 13:14:45 +01001224remove:
Michal Vasko428087d2016-01-14 16:04:28 +01001225 --ps->session_count;
fanchanghu3d4e7212017-08-09 09:42:30 +08001226 if (i <= ps->session_count) {
1227 free(ps->sessions[i]);
Michal Vasko58005732016-02-02 15:50:52 +01001228 ps->sessions[i] = ps->sessions[ps->session_count];
fanchanghu3d4e7212017-08-09 09:42:30 +08001229 }
1230 if (!ps->session_count) {
Michal Vasko58005732016-02-02 15:50:52 +01001231 free(ps->sessions);
1232 ps->sessions = NULL;
Michal Vasko58005732016-02-02 15:50:52 +01001233 }
Michal Vasko11d2f6a2017-02-02 11:15:06 +01001234 ps->last_event_session = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001235 return 0;
1236 }
1237 }
1238
Michal Vaskof0537d82016-01-29 14:42:38 +01001239 return -1;
Michal Vasko428087d2016-01-14 16:04:28 +01001240}
1241
Michal Vasko48a63ed2016-03-01 09:48:21 +01001242API int
1243nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
1244{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001245 uint8_t q_id;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001246 int ret, ret2;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001247
romanf578cd52023-10-19 09:47:40 +02001248 NC_CHECK_ARG_RET(session, ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001249
1250 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001251 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001252 return -1;
1253 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001254
Radek Krejcid5f978f2016-03-03 13:14:45 +01001255 ret = _nc_ps_del_session(ps, session, -1);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001256
1257 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001258 ret2 = nc_ps_unlock(ps, q_id, __func__);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001259
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001260 return ret || ret2 ? -1 : 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001261}
1262
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001263API struct nc_session *
Michal Vasko4871c9d2017-10-09 14:48:39 +02001264nc_ps_get_session(const struct nc_pollsession *ps, uint16_t idx)
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001265{
1266 uint8_t q_id;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001267 struct nc_session *ret = NULL;
1268
romanf578cd52023-10-19 09:47:40 +02001269 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001270
1271 /* LOCK */
1272 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1273 return NULL;
1274 }
1275
Michal Vasko4871c9d2017-10-09 14:48:39 +02001276 if (idx < ps->session_count) {
1277 ret = ps->sessions[idx]->session;
Michal Vaskoe1ee05b2017-03-21 10:10:18 +01001278 }
1279
1280 /* UNLOCK */
1281 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1282
1283 return ret;
1284}
1285
Michal Vasko3ec3b112022-07-21 12:32:33 +02001286API struct nc_session *
1287nc_ps_find_session(const struct nc_pollsession *ps, nc_ps_session_match_cb match_cb, void *cb_data)
1288{
1289 uint8_t q_id;
1290 uint16_t i;
1291 struct nc_session *ret = NULL;
1292
romanf578cd52023-10-19 09:47:40 +02001293 NC_CHECK_ARG_RET(NULL, ps, NULL);
Michal Vasko3ec3b112022-07-21 12:32:33 +02001294
1295 /* LOCK */
1296 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1297 return NULL;
1298 }
1299
1300 for (i = 0; i < ps->session_count; ++i) {
1301 if (match_cb(ps->sessions[i]->session, cb_data)) {
1302 ret = ps->sessions[i]->session;
1303 break;
1304 }
1305 }
1306
1307 /* UNLOCK */
1308 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1309
1310 return ret;
1311}
1312
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001313API uint16_t
1314nc_ps_session_count(struct nc_pollsession *ps)
1315{
Michal Vasko47003942019-03-14 12:25:23 +01001316 uint8_t q_id;
1317 uint16_t session_count;
1318
romanf578cd52023-10-19 09:47:40 +02001319 NC_CHECK_ARG_RET(NULL, ps, 0);
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001320
Michal Vasko47003942019-03-14 12:25:23 +01001321 /* LOCK (just for memory barrier so that we read the current value) */
1322 if (nc_ps_lock((struct nc_pollsession *)ps, &q_id, __func__)) {
1323 return 0;
1324 }
1325
1326 session_count = ps->session_count;
1327
1328 /* UNLOCK */
1329 nc_ps_unlock((struct nc_pollsession *)ps, q_id, __func__);
1330
1331 return session_count;
Michal Vasko0fdb7ac2016-03-01 09:03:12 +01001332}
1333
Michal Vasko77e83572022-07-21 15:31:15 +02001334static NC_MSG_TYPE
1335recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
1336{
1337 struct lyd_attr *attr;
1338
1339 assert(envp && !envp->schema);
1340
1341 /* find the message-id attribute */
1342 LY_LIST_FOR(((struct lyd_node_opaq *)envp)->attr, attr) {
1343 if (!strcmp(attr->name.name, "message-id")) {
1344 break;
1345 }
1346 }
1347
1348 if (!attr) {
1349 ERR(session, "Received an <rpc> without a message-id.");
1350 return NC_MSG_REPLY_ERR_MSGID;
1351 }
1352
1353 return NC_MSG_RPC;
1354}
1355
Michal Vasko131120a2018-05-29 15:44:02 +02001356/* should be called holding the session RPC lock! IO lock will be acquired as needed
Michal Vasko71090fc2016-05-24 16:37:28 +02001357 * returns: NC_PSPOLL_ERROR,
Michal Vasko77367452021-02-16 16:32:18 +01001358 * NC_PSPOLL_TIMEOUT,
Michal Vaskof8fba542023-10-23 12:03:50 +02001359 * NC_PSPOLL_BAD_RPC (| NC_PSPOLL_REPLY_ERROR),
Michal Vasko71090fc2016-05-24 16:37:28 +02001360 * NC_PSPOLL_RPC
1361 */
1362static int
Michal Vasko131120a2018-05-29 15:44:02 +02001363nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_server_rpc **rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001364{
Michal Vasko77367452021-02-16 16:32:18 +01001365 struct ly_in *msg;
Radek Krejcif93c7d42016-04-06 13:41:15 +02001366 struct nc_server_reply *reply = NULL;
Michal Vasko939ffce2021-04-12 13:02:01 +02001367 struct lyd_node *e;
Michal Vaskof8fba542023-10-23 12:03:50 +02001368 int r, ret = 0;
Michal Vasko428087d2016-01-14 16:04:28 +01001369
romanf578cd52023-10-19 09:47:40 +02001370 NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
1371
1372 if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vasko05532772021-06-03 12:12:38 +02001373 ERR(session, "Invalid session to receive RPCs.");
Michal Vasko71090fc2016-05-24 16:37:28 +02001374 return NC_PSPOLL_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +01001375 }
1376
Michal Vasko93224072021-11-09 12:14:28 +01001377 *rpc = NULL;
1378
Michal Vasko77367452021-02-16 16:32:18 +01001379 /* get a message */
1380 r = nc_read_msg_io(session, io_timeout, &msg, 0);
1381 if (r == -2) {
1382 /* malformed message */
Michal Vasko93224072021-11-09 12:14:28 +01001383 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vasko77e83572022-07-21 15:31:15 +02001384 goto cleanup;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001385 }
1386 if (r == -1) {
Michal Vasko77367452021-02-16 16:32:18 +01001387 return NC_PSPOLL_ERROR;
1388 } else if (!r) {
1389 return NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001390 }
1391
Michal Vasko77367452021-02-16 16:32:18 +01001392 *rpc = calloc(1, sizeof **rpc);
roman3a95bb22023-10-26 11:07:17 +02001393 NC_CHECK_ERRMEM_GOTO(!*rpc, ret = NC_PSPOLL_ERROR, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +01001394
1395 /* parse the RPC */
Michal Vasko77e83572022-07-21 15:31:15 +02001396 if (!lyd_parse_op(session->ctx, NULL, msg, LYD_XML, LYD_TYPE_RPC_NETCONF, &(*rpc)->envp, &(*rpc)->rpc)) {
1397 /* check message-id */
1398 if (recv_rpc_check_msgid(session, (*rpc)->envp) == NC_MSG_RPC) {
1399 /* valid RPC */
1400 ret = NC_PSPOLL_RPC;
1401 } else {
1402 /* no message-id */
Michal Vasko77e83572022-07-21 15:31:15 +02001403 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
1404 }
1405 } else {
Michal Vasko77367452021-02-16 16:32:18 +01001406 /* bad RPC received */
Michal Vasko77367452021-02-16 16:32:18 +01001407 if ((*rpc)->envp) {
1408 /* at least the envelopes were parsed */
Michal Vasko93224072021-11-09 12:14:28 +01001409 e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
1410 nc_err_set_msg(e, ly_errmsg(session->ctx), "en");
Michal Vasko939ffce2021-04-12 13:02:01 +02001411 reply = nc_server_reply_err(e);
Michal Vasko77367452021-02-16 16:32:18 +01001412 } else if (session->version == NC_VERSION_11) {
Michal Vasko93224072021-11-09 12:14:28 +01001413 /* completely malformed message, NETCONF version 1.1 defines sending error reply from
1414 * the server (RFC 6241 sec. 3) */
1415 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
Michal Vaskof8fba542023-10-23 12:03:50 +02001416 } else {
1417 /* at least set the return value */
1418 ret = NC_PSPOLL_BAD_RPC;
Michal Vasko77367452021-02-16 16:32:18 +01001419 }
Michal Vasko77367452021-02-16 16:32:18 +01001420 }
1421
1422cleanup:
Michal Vasko77e83572022-07-21 15:31:15 +02001423 if (reply) {
1424 /* send error reply */
1425 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, *rpc ? (*rpc)->envp : NULL, reply);
1426 nc_server_reply_free(reply);
1427 if (r != NC_MSG_REPLY) {
1428 ERR(session, "Failed to write reply (%s), terminating session.", nc_msgtype2str[r]);
1429 if (session->status != NC_STATUS_INVALID) {
1430 session->status = NC_STATUS_INVALID;
1431 session->term_reason = NC_SESSION_TERM_OTHER;
1432 }
1433 }
Michal Vaskof8fba542023-10-23 12:03:50 +02001434
1435 /* bad RPC and an error reply sent */
1436 ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
Michal Vasko77e83572022-07-21 15:31:15 +02001437 }
1438
Michal Vasko77367452021-02-16 16:32:18 +01001439 ly_in_free(msg, 1);
1440 if (ret != NC_PSPOLL_RPC) {
1441 nc_server_rpc_free(*rpc);
1442 *rpc = NULL;
1443 }
Michal Vasko71090fc2016-05-24 16:37:28 +02001444 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001445}
1446
fanchanghu966f2de2016-07-21 02:28:57 -04001447API void
1448nc_set_global_rpc_clb(nc_rpc_clb clb)
1449{
1450 global_rpc_clb = clb;
1451}
1452
Radek Krejci93e80222016-10-03 13:34:25 +02001453API NC_MSG_TYPE
1454nc_server_notif_send(struct nc_session *session, struct nc_server_notif *notif, int timeout)
1455{
Michal Vasko131120a2018-05-29 15:44:02 +02001456 NC_MSG_TYPE ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001457
1458 /* check parameters */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001459 if (!session || (session->side != NC_SERVER) || !nc_session_get_notif_status(session)) {
romanf578cd52023-10-19 09:47:40 +02001460 ERRARG(NULL, "session");
Radek Krejci93e80222016-10-03 13:34:25 +02001461 return NC_MSG_ERROR;
Michal Vasko77367452021-02-16 16:32:18 +01001462 } else if (!notif || !notif->ntf || !notif->eventtime) {
romanf578cd52023-10-19 09:47:40 +02001463 ERRARG(NULL, "notif");
Radek Krejci93e80222016-10-03 13:34:25 +02001464 return NC_MSG_ERROR;
1465 }
1466
Michal Vasko131120a2018-05-29 15:44:02 +02001467 /* we do not need RPC lock for this, IO lock will be acquired properly */
1468 ret = nc_write_msg_io(session, timeout, NC_MSG_NOTIF, notif);
Michal Vasko8fe604c2020-02-10 15:25:04 +01001469 if (ret != NC_MSG_NOTIF) {
Michal Vasko05532772021-06-03 12:12:38 +02001470 ERR(session, "Failed to write notification (%s).", nc_msgtype2str[ret]);
Radek Krejci93e80222016-10-03 13:34:25 +02001471 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001472
Michal Vasko131120a2018-05-29 15:44:02 +02001473 return ret;
Radek Krejci93e80222016-10-03 13:34:25 +02001474}
1475
Michal Vaskof9467762023-03-28 09:02:08 +02001476/**
1477 * @brief Send a reply acquiring IO lock as needed.
1478 * Session RPC lock must be held!
1479 *
1480 * @param[in] session Session to use.
1481 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1482 * @param[in] rpc RPC to sent.
1483 * @return 0 on success.
1484 * @return Bitmask of NC_PSPOLL_ERROR (any fatal error) and NC_PSPOLL_REPLY_ERROR (reply failed to be sent).
1485 * @return NC_PSPOLL_ERROR on other errors.
Michal Vasko71090fc2016-05-24 16:37:28 +02001486 */
1487static int
Michal Vasko93224072021-11-09 12:14:28 +01001488nc_server_send_reply_io(struct nc_session *session, int io_timeout, const struct nc_server_rpc *rpc)
Michal Vasko428087d2016-01-14 16:04:28 +01001489{
1490 nc_rpc_clb clb;
1491 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +01001492 const struct lysc_node *rpc_act = NULL;
1493 struct lyd_node *elem;
Michal Vasko131120a2018-05-29 15:44:02 +02001494 int ret = 0;
1495 NC_MSG_TYPE r;
Michal Vasko428087d2016-01-14 16:04:28 +01001496
Michal Vasko4a827e52016-03-03 10:59:00 +01001497 if (!rpc) {
1498 ERRINT;
Michal Vasko71090fc2016-05-24 16:37:28 +02001499 return NC_PSPOLL_ERROR;
Michal Vasko4a827e52016-03-03 10:59:00 +01001500 }
1501
Michal Vasko77367452021-02-16 16:32:18 +01001502 if (rpc->rpc->schema->nodetype == LYS_RPC) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001503 /* RPC */
Michal Vasko77367452021-02-16 16:32:18 +01001504 rpc_act = rpc->rpc->schema;
fanchanghu966f2de2016-07-21 02:28:57 -04001505 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001506 /* action */
Michal Vasko77367452021-02-16 16:32:18 +01001507 LYD_TREE_DFS_BEGIN(rpc->rpc, elem) {
Michal Vasko90e8e692016-07-13 12:27:57 +02001508 if (elem->schema->nodetype == LYS_ACTION) {
1509 rpc_act = elem->schema;
1510 break;
1511 }
Michal Vasko77367452021-02-16 16:32:18 +01001512 LYD_TREE_DFS_END(rpc->rpc, elem);
fanchanghu966f2de2016-07-21 02:28:57 -04001513 }
Michal Vasko90e8e692016-07-13 12:27:57 +02001514 if (!rpc_act) {
1515 ERRINT;
1516 return NC_PSPOLL_ERROR;
1517 }
1518 }
1519
1520 if (!rpc_act->priv) {
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001521 if (!global_rpc_clb) {
1522 /* no callback, reply with a not-implemented error */
Michal Vasko93224072021-11-09 12:14:28 +01001523 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 +03001524 } else {
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001525 reply = global_rpc_clb(rpc->rpc, session);
Petr A. Sokolov16284ca2019-02-04 09:02:40 +03001526 }
Michal Vasko428087d2016-01-14 16:04:28 +01001527 } else {
Michal Vasko90e8e692016-07-13 12:27:57 +02001528 clb = (nc_rpc_clb)rpc_act->priv;
Michal Vasko77367452021-02-16 16:32:18 +01001529 reply = clb(rpc->rpc, session);
Michal Vasko428087d2016-01-14 16:04:28 +01001530 }
1531
1532 if (!reply) {
Michal Vasko93224072021-11-09 12:14:28 +01001533 reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +01001534 }
Michal Vasko77367452021-02-16 16:32:18 +01001535 r = nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, rpc->envp, reply);
Michal Vasko71090fc2016-05-24 16:37:28 +02001536 if (reply->type == NC_RPL_ERROR) {
1537 ret |= NC_PSPOLL_REPLY_ERROR;
1538 }
1539 nc_server_reply_free(reply);
Michal Vasko428087d2016-01-14 16:04:28 +01001540
Michal Vasko131120a2018-05-29 15:44:02 +02001541 if (r != NC_MSG_REPLY) {
Michal Vasko15469492021-06-09 08:40:48 +02001542 ERR(session, "Failed to write reply (%s).", nc_msgtype2str[r]);
Michal Vasko71090fc2016-05-24 16:37:28 +02001543 ret |= NC_PSPOLL_ERROR;
1544 }
Michal Vasko428087d2016-01-14 16:04:28 +01001545
1546 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
1547 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
1548 session->status = NC_STATUS_INVALID;
1549 }
1550
Michal Vasko71090fc2016-05-24 16:37:28 +02001551 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001552}
1553
Michal Vaskof9467762023-03-28 09:02:08 +02001554/**
1555 * @brief Poll a session from pspoll acquiring IO lock as needed.
1556 * Session must be running and session RPC lock held!
1557 *
1558 * @param[in] session Session to use.
1559 * @param[in] io_timeout Timeout to use for acquiring IO lock.
1560 * @param[in] now_mono Current monotonic timestamp.
1561 * @param[in,out] msg Message to fill in case of an error.
1562 * @return NC_PSPOLL_RPC if some application data are available.
1563 * @return NC_PSPOLL_TIMEOUT if a timeout elapsed.
1564 * @return NC_PSPOLL_SSH_CHANNEL if a new SSH channel has been created.
1565 * @return NC_PSPOLL_SSH_MSG if just an SSH message has been processed.
1566 * @return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR if session has been terminated (@p msg filled).
1567 * @return NC_PSPOLL_ERROR on other fatal errors (@p msg filled).
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001568 */
1569static int
Michal Vasko131120a2018-05-29 15:44:02 +02001570nc_ps_poll_session_io(struct nc_session *session, int io_timeout, time_t now_mono, char *msg)
Michal Vasko428087d2016-01-14 16:04:28 +01001571{
Michal Vasko9a327362017-01-11 11:31:46 +01001572 struct pollfd pfd;
Michal Vasko2260f552018-06-06 10:06:12 +02001573 int r, ret = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001574
romanf578cd52023-10-19 09:47:40 +02001575#ifdef NC_ENABLED_SSH_TLS
roman456f92d2023-04-28 10:28:12 +02001576 ssh_message ssh_msg;
Michal Vasko9a327362017-01-11 11:31:46 +01001577 struct nc_session *new;
romanf578cd52023-10-19 09:47:40 +02001578#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko428087d2016-01-14 16:04:28 +01001579
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001580 /* check timeout first */
Michal Vaskodf68e7e2022-04-21 11:04:00 +02001581 if (!(session->flags & NC_SESSION_CALLHOME) && !nc_session_get_notif_status(session) && server_opts.idle_timeout &&
romanf578cd52023-10-19 09:47:40 +02001582 (now_mono >= session->opts.server.last_rpc + (unsigned) server_opts.idle_timeout)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001583 sprintf(msg, "session idle timeout elapsed");
1584 session->status = NC_STATUS_INVALID;
1585 session->term_reason = NC_SESSION_TERM_TIMEOUT;
1586 return NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1587 }
1588
Michal Vasko131120a2018-05-29 15:44:02 +02001589 r = nc_session_io_lock(session, io_timeout, __func__);
1590 if (r < 0) {
1591 sprintf(msg, "session IO lock failed to be acquired");
1592 return NC_PSPOLL_ERROR;
1593 } else if (!r) {
1594 return NC_PSPOLL_TIMEOUT;
1595 }
1596
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001597 switch (session->ti_type) {
romanf578cd52023-10-19 09:47:40 +02001598#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001599 case NC_TI_LIBSSH:
romanf578cd52023-10-19 09:47:40 +02001600 ssh_msg = ssh_message_get(session->ti.libssh.session);
1601 if (ssh_msg) {
1602 nc_session_ssh_msg(session, NULL, ssh_msg, NULL);
1603 if (session->ti.libssh.next) {
1604 for (new = session->ti.libssh.next; new != session; new = new->ti.libssh.next) {
1605 if ((new->status == NC_STATUS_STARTING) && new->ti.libssh.channel &&
1606 (new->flags & NC_SESSION_SSH_SUBSYS_NETCONF)) {
1607 /* new NETCONF SSH channel */
1608 ret = NC_PSPOLL_SSH_CHANNEL;
1609 break;
1610 }
1611 }
1612 if (new != session) {
1613 ssh_message_free(ssh_msg);
1614 break;
1615 }
1616 }
1617 if (!ret) {
1618 /* just some SSH message */
1619 ret = NC_PSPOLL_SSH_MSG;
1620 }
1621 ssh_message_free(ssh_msg);
1622
1623 /* break because 1) we don't want to return anything here ORred with NC_PSPOLL_RPC
1624 * and 2) we don't want to delay openning a new channel by waiting for a RPC to get processed
1625 */
1626 break;
1627 }
1628
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001629 r = ssh_channel_poll_timeout(session->ti.libssh.channel, 0, 0);
Michal Vasko8dcaa882017-10-19 14:28:42 +02001630 if (r == SSH_EOF) {
1631 sprintf(msg, "SSH channel unexpected EOF");
1632 session->status = NC_STATUS_INVALID;
1633 session->term_reason = NC_SESSION_TERM_DROPPED;
1634 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1635 } else if (r == SSH_ERROR) {
1636 sprintf(msg, "SSH channel poll error (%s)", ssh_get_error(session->ti.libssh.session));
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001637 session->status = NC_STATUS_INVALID;
1638 session->term_reason = NC_SESSION_TERM_OTHER;
1639 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
Michal Vasko8dcaa882017-10-19 14:28:42 +02001640 } else if (!r) {
romanf578cd52023-10-19 09:47:40 +02001641 /* no application data received */
1642 ret = NC_PSPOLL_TIMEOUT;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001643 } else {
1644 /* we have some application data */
1645 ret = NC_PSPOLL_RPC;
1646 }
1647 break;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001648 case NC_TI_OPENSSL:
1649 r = SSL_pending(session->ti.tls);
1650 if (!r) {
1651 /* no data pending in the SSL buffer, poll fd */
1652 pfd.fd = SSL_get_rfd(session->ti.tls);
1653 if (pfd.fd < 0) {
1654 sprintf(msg, "internal error (%s:%d)", __FILE__, __LINE__);
1655 ret = NC_PSPOLL_ERROR;
1656 break;
1657 }
1658 pfd.events = POLLIN;
1659 pfd.revents = 0;
1660 r = poll(&pfd, 1, 0);
1661
1662 if ((r < 0) && (errno != EINTR)) {
1663 sprintf(msg, "poll failed (%s)", strerror(errno));
1664 session->status = NC_STATUS_INVALID;
1665 ret = NC_PSPOLL_ERROR;
1666 } else if (r > 0) {
1667 if (pfd.revents & (POLLHUP | POLLNVAL)) {
1668 sprintf(msg, "communication socket unexpectedly closed");
1669 session->status = NC_STATUS_INVALID;
1670 session->term_reason = NC_SESSION_TERM_DROPPED;
1671 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1672 } else if (pfd.revents & POLLERR) {
1673 sprintf(msg, "communication socket error");
1674 session->status = NC_STATUS_INVALID;
1675 session->term_reason = NC_SESSION_TERM_OTHER;
1676 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1677 } else {
1678 ret = NC_PSPOLL_RPC;
1679 }
1680 } else {
1681 ret = NC_PSPOLL_TIMEOUT;
1682 }
1683 } else {
1684 ret = NC_PSPOLL_RPC;
1685 }
1686 break;
romanf578cd52023-10-19 09:47:40 +02001687#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001688 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +02001689 case NC_TI_UNIX:
1690 pfd.fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001691 pfd.events = POLLIN;
1692 pfd.revents = 0;
1693 r = poll(&pfd, 1, 0);
1694
1695 if ((r < 0) && (errno != EINTR)) {
1696 sprintf(msg, "poll failed (%s)", strerror(errno));
1697 session->status = NC_STATUS_INVALID;
1698 ret = NC_PSPOLL_ERROR;
1699 } else if (r > 0) {
1700 if (pfd.revents & (POLLHUP | POLLNVAL)) {
1701 sprintf(msg, "communication socket unexpectedly closed");
1702 session->status = NC_STATUS_INVALID;
1703 session->term_reason = NC_SESSION_TERM_DROPPED;
1704 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1705 } else if (pfd.revents & POLLERR) {
1706 sprintf(msg, "communication socket error");
1707 session->status = NC_STATUS_INVALID;
1708 session->term_reason = NC_SESSION_TERM_OTHER;
1709 ret = NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
1710 } else {
1711 ret = NC_PSPOLL_RPC;
1712 }
1713 } else {
1714 ret = NC_PSPOLL_TIMEOUT;
1715 }
1716 break;
1717 case NC_TI_NONE:
1718 sprintf(msg, "internal error (%s:%d)", __FILE__, __LINE__);
1719 ret = NC_PSPOLL_ERROR;
1720 break;
1721 }
1722
Michal Vasko131120a2018-05-29 15:44:02 +02001723 nc_session_io_unlock(session, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001724 return ret;
1725}
1726
1727API int
1728nc_ps_poll(struct nc_pollsession *ps, int timeout, struct nc_session **session)
1729{
Michal Vasko443faa02022-10-20 09:09:03 +02001730 int ret = NC_PSPOLL_ERROR, r;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001731 uint8_t q_id;
1732 uint16_t i, j;
1733 char msg[256];
1734 struct timespec ts_timeout, ts_cur;
1735 struct nc_session *cur_session;
fanchanghu3d4e7212017-08-09 09:42:30 +08001736 struct nc_ps_session *cur_ps_session;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001737 struct nc_server_rpc *rpc = NULL;
1738
romanf578cd52023-10-19 09:47:40 +02001739 NC_CHECK_ARG_RET(NULL, ps, NC_PSPOLL_ERROR);
Michal Vasko428087d2016-01-14 16:04:28 +01001740
Michal Vaskoade892d2017-02-22 13:40:35 +01001741 /* PS LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001742 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vasko71090fc2016-05-24 16:37:28 +02001743 return NC_PSPOLL_ERROR;
Michal Vaskobe86fe32016-04-07 10:43:03 +02001744 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001745
Michal Vaskoade892d2017-02-22 13:40:35 +01001746 if (!ps->session_count) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001747 nc_ps_unlock(ps, q_id, __func__);
1748 return NC_PSPOLL_NOSESSIONS;
Michal Vaskoade892d2017-02-22 13:40:35 +01001749 }
Michal Vaskobd8ef262016-01-20 11:09:27 +01001750
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001751 /* fill timespecs */
Michal Vaskod8a74192023-02-06 15:51:50 +01001752 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko36c7be82017-02-22 13:37:59 +01001753 if (timeout > -1) {
Michal Vaskod8a74192023-02-06 15:51:50 +01001754 nc_timeouttime_get(&ts_timeout, timeout);
Michal Vasko36c7be82017-02-22 13:37:59 +01001755 }
1756
1757 /* poll all the sessions one-by-one */
Michal Vasko9a327362017-01-11 11:31:46 +01001758 do {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001759 /* loop from i to j once (all sessions) */
Michal Vasko9a327362017-01-11 11:31:46 +01001760 if (ps->last_event_session == ps->session_count - 1) {
1761 i = j = 0;
1762 } else {
1763 i = j = ps->last_event_session + 1;
Michal Vaskobd8ef262016-01-20 11:09:27 +01001764 }
Michal Vasko9a327362017-01-11 11:31:46 +01001765 do {
fanchanghu3d4e7212017-08-09 09:42:30 +08001766 cur_ps_session = ps->sessions[i];
1767 cur_session = cur_ps_session->session;
Michal Vasko428087d2016-01-14 16:04:28 +01001768
Michal Vasko131120a2018-05-29 15:44:02 +02001769 /* SESSION RPC LOCK */
1770 r = nc_session_rpc_lock(cur_session, 0, __func__);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001771 if (r == -1) {
1772 ret = NC_PSPOLL_ERROR;
1773 } else if (r == 1) {
1774 /* no one else is currently working with the session, so we can, otherwise skip it */
Michal Vaskoc97cb162017-10-16 12:10:23 +02001775 switch (cur_ps_session->state) {
1776 case NC_PS_STATE_NONE:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001777 if (cur_session->status == NC_STATUS_RUNNING) {
1778 /* session is fine, work with it */
fanchanghu3d4e7212017-08-09 09:42:30 +08001779 cur_ps_session->state = NC_PS_STATE_BUSY;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001780
Michal Vasko131120a2018-05-29 15:44:02 +02001781 ret = nc_ps_poll_session_io(cur_session, NC_SESSION_LOCK_TIMEOUT, ts_cur.tv_sec, msg);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001782 switch (ret) {
1783 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001784 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001785 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001786 break;
1787 case NC_PSPOLL_ERROR:
Michal Vasko05532772021-06-03 12:12:38 +02001788 ERR(cur_session, "%s.", msg);
fanchanghu3d4e7212017-08-09 09:42:30 +08001789 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001790 break;
1791 case NC_PSPOLL_TIMEOUT:
romanf578cd52023-10-19 09:47:40 +02001792#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001793 case NC_PSPOLL_SSH_CHANNEL:
1794 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001795#endif /* NC_ENABLED_SSH_TLS */
fanchanghu3d4e7212017-08-09 09:42:30 +08001796 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001797 break;
1798 case NC_PSPOLL_RPC:
1799 /* let's keep the state busy, we are not done with this session */
1800 break;
1801 }
1802 } else {
1803 /* session is not fine, let the caller know */
1804 ret = NC_PSPOLL_SESSION_TERM;
1805 if (cur_session->term_reason != NC_SESSION_TERM_CLOSED) {
1806 ret |= NC_PSPOLL_SESSION_ERROR;
1807 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001808 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001809 }
Michal Vaskoc97cb162017-10-16 12:10:23 +02001810 break;
1811 case NC_PS_STATE_BUSY:
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001812 /* it definitely should not be busy because we have the lock */
1813 ERRINT;
Michal Vasko4992d802017-08-09 12:20:01 +02001814 ret = NC_PSPOLL_ERROR;
Michal Vaskoc97cb162017-10-16 12:10:23 +02001815 break;
1816 case NC_PS_STATE_INVALID:
1817 /* we got it locked, but it will be freed, let it be */
1818 ret = NC_PSPOLL_TIMEOUT;
1819 break;
Michal Vasko428087d2016-01-14 16:04:28 +01001820 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001821
Michal Vasko131120a2018-05-29 15:44:02 +02001822 /* keep RPC lock in this one case */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001823 if (ret != NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001824 /* SESSION RPC UNLOCK */
1825 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vaskoade892d2017-02-22 13:40:35 +01001826 }
Michal Vaskoade892d2017-02-22 13:40:35 +01001827 } else {
1828 /* timeout */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001829 ret = NC_PSPOLL_TIMEOUT;
Michal Vasko428087d2016-01-14 16:04:28 +01001830 }
Michal Vasko428087d2016-01-14 16:04:28 +01001831
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001832 /* something happened */
1833 if (ret != NC_PSPOLL_TIMEOUT) {
1834 break;
1835 }
1836
Michal Vasko9a327362017-01-11 11:31:46 +01001837 if (i == ps->session_count - 1) {
1838 i = 0;
1839 } else {
1840 ++i;
1841 }
1842 } while (i != j);
1843
Michal Vaskoade892d2017-02-22 13:40:35 +01001844 /* no event, no session remains locked */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001845 if (ret == NC_PSPOLL_TIMEOUT) {
Michal Vasko9a327362017-01-11 11:31:46 +01001846 usleep(NC_TIMEOUT_STEP);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001847
Michal Vaskod8a74192023-02-06 15:51:50 +01001848 if ((timeout > -1) && (nc_timeouttime_cur_diff(&ts_timeout) < 1)) {
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001849 /* final timeout */
1850 break;
Michal Vasko9a327362017-01-11 11:31:46 +01001851 }
Michal Vasko428087d2016-01-14 16:04:28 +01001852 }
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001853 } while (ret == NC_PSPOLL_TIMEOUT);
Michal Vasko428087d2016-01-14 16:04:28 +01001854
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001855 /* do we want to return the session? */
1856 switch (ret) {
1857 case NC_PSPOLL_RPC:
1858 case NC_PSPOLL_SESSION_TERM:
1859 case NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR:
romanf578cd52023-10-19 09:47:40 +02001860#ifdef NC_ENABLED_SSH_TLS
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001861 case NC_PSPOLL_SSH_CHANNEL:
1862 case NC_PSPOLL_SSH_MSG:
romanf578cd52023-10-19 09:47:40 +02001863#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001864 if (session) {
1865 *session = cur_session;
1866 }
1867 ps->last_event_session = i;
1868 break;
1869 default:
1870 break;
Michal Vasko71090fc2016-05-24 16:37:28 +02001871 }
Michal Vasko428087d2016-01-14 16:04:28 +01001872
Michal Vaskoade892d2017-02-22 13:40:35 +01001873 /* PS UNLOCK */
1874 nc_ps_unlock(ps, q_id, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001875
Michal Vasko131120a2018-05-29 15:44:02 +02001876 /* we have some data available and the session is RPC locked (but not IO locked) */
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001877 if (ret == NC_PSPOLL_RPC) {
Michal Vasko131120a2018-05-29 15:44:02 +02001878 ret = nc_server_recv_rpc_io(cur_session, timeout, &rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001879 if (ret & (NC_PSPOLL_ERROR | NC_PSPOLL_BAD_RPC)) {
1880 if (cur_session->status != NC_STATUS_RUNNING) {
1881 ret |= NC_PSPOLL_SESSION_TERM | NC_PSPOLL_SESSION_ERROR;
fanchanghu3d4e7212017-08-09 09:42:30 +08001882 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001883 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001884 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001885 }
1886 } else {
Michal Vasko9fb42272017-10-05 13:50:05 +02001887 cur_session->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001888
Michal Vasko7f1ee932018-10-11 09:41:42 +02001889 /* process RPC */
Michal Vasko131120a2018-05-29 15:44:02 +02001890 ret |= nc_server_send_reply_io(cur_session, timeout, rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001891 if (cur_session->status != NC_STATUS_RUNNING) {
1892 ret |= NC_PSPOLL_SESSION_TERM;
1893 if (!(cur_session->term_reason & (NC_SESSION_TERM_CLOSED | NC_SESSION_TERM_KILLED))) {
1894 ret |= NC_PSPOLL_SESSION_ERROR;
1895 }
fanchanghu3d4e7212017-08-09 09:42:30 +08001896 cur_ps_session->state = NC_PS_STATE_INVALID;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001897 } else {
fanchanghu3d4e7212017-08-09 09:42:30 +08001898 cur_ps_session->state = NC_PS_STATE_NONE;
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001899 }
Michal Vasko428087d2016-01-14 16:04:28 +01001900 }
Michal Vasko77367452021-02-16 16:32:18 +01001901 nc_server_rpc_free(rpc);
Michal Vaskoefbc5e32017-05-26 14:02:16 +02001902
Michal Vasko131120a2018-05-29 15:44:02 +02001903 /* SESSION RPC UNLOCK */
1904 nc_session_rpc_unlock(cur_session, NC_SESSION_LOCK_TIMEOUT, __func__);
Michal Vasko428087d2016-01-14 16:04:28 +01001905 }
1906
Michal Vasko48a63ed2016-03-01 09:48:21 +01001907 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +01001908}
1909
Michal Vaskod09eae62016-02-01 10:32:52 +01001910API void
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001911nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
Michal Vaskod09eae62016-02-01 10:32:52 +01001912{
Michal Vaskob30b99c2016-07-26 11:35:43 +02001913 uint8_t q_id;
Michal Vaskod09eae62016-02-01 10:32:52 +01001914 uint16_t i;
1915 struct nc_session *session;
1916
Michal Vasko9a25e932016-02-01 10:36:42 +01001917 if (!ps) {
romanf578cd52023-10-19 09:47:40 +02001918 ERRARG(NULL, "ps");
Michal Vasko9a25e932016-02-01 10:36:42 +01001919 return;
1920 }
1921
Michal Vasko48a63ed2016-03-01 09:48:21 +01001922 /* LOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001923 if (nc_ps_lock(ps, &q_id, __func__)) {
Michal Vaskobe86fe32016-04-07 10:43:03 +02001924 return;
1925 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001926
Michal Vasko48a63ed2016-03-01 09:48:21 +01001927 if (all) {
Radek Krejci4f8042c2016-03-03 13:11:26 +01001928 for (i = 0; i < ps->session_count; i++) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001929 nc_session_free(ps->sessions[i]->session, data_free);
1930 free(ps->sessions[i]);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001931 }
1932 free(ps->sessions);
1933 ps->sessions = NULL;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001934 ps->session_count = 0;
Michal Vasko9a327362017-01-11 11:31:46 +01001935 ps->last_event_session = 0;
Michal Vasko48a63ed2016-03-01 09:48:21 +01001936 } else {
1937 for (i = 0; i < ps->session_count; ) {
fanchanghu3d4e7212017-08-09 09:42:30 +08001938 if (ps->sessions[i]->session->status != NC_STATUS_RUNNING) {
1939 session = ps->sessions[i]->session;
Radek Krejcid5f978f2016-03-03 13:14:45 +01001940 _nc_ps_del_session(ps, NULL, i);
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01001941 nc_session_free(session, data_free);
Michal Vasko48a63ed2016-03-01 09:48:21 +01001942 continue;
1943 }
1944
1945 ++i;
1946 }
Michal Vaskod09eae62016-02-01 10:32:52 +01001947 }
Michal Vasko48a63ed2016-03-01 09:48:21 +01001948
1949 /* UNLOCK */
Michal Vasko26043172016-07-26 14:08:59 +02001950 nc_ps_unlock(ps, q_id, __func__);
Michal Vaskod09eae62016-02-01 10:32:52 +01001951}
1952
romanfb3f7cf2023-11-30 16:10:09 +01001953int
1954nc_server_set_address_port(struct nc_endpt *endpt, struct nc_bind *bind, const char *address, uint16_t port)
1955{
1956 int sock = -1, set_addr, ret = 0;
1957
1958 assert((address && !port) || (!address && port) || (endpt->ti == NC_TI_UNIX));
1959
1960 if (address) {
1961 set_addr = 1;
1962 } else {
1963 set_addr = 0;
1964 }
1965
1966 if (set_addr) {
1967 port = bind->port;
1968 } else {
1969 address = bind->address;
1970 }
1971
1972 /* we have all the information we need to create a listening socket */
1973 if ((address && port) || (endpt->ti == NC_TI_UNIX)) {
1974 /* create new socket, close the old one */
1975 if (endpt->ti == NC_TI_UNIX) {
1976 sock = nc_sock_listen_unix(endpt->opts.unixsock);
1977 } else {
1978 sock = nc_sock_listen_inet(address, port, &endpt->ka);
1979 }
1980
1981 if (sock == -1) {
1982 ret = 1;
1983 goto cleanup;
1984 }
1985
1986 if (bind->sock > -1) {
1987 close(bind->sock);
1988 }
1989 bind->sock = sock;
1990 }
1991
1992 if (sock > -1) {
1993 switch (endpt->ti) {
1994 case NC_TI_UNIX:
1995 VRB(NULL, "Listening on %s for UNIX connections.", endpt->opts.unixsock->address);
1996 break;
1997#ifdef NC_ENABLED_SSH_TLS
1998 case NC_TI_LIBSSH:
1999 VRB(NULL, "Listening on %s:%u for SSH connections.", address, port);
2000 break;
2001 case NC_TI_OPENSSL:
2002 VRB(NULL, "Listening on %s:%u for TLS connections.", address, port);
2003 break;
2004#endif /* NC_ENABLED_SSH_TLS */
2005 default:
2006 ERRINT;
2007 ret = 1;
2008 break;
2009 }
2010 }
2011
2012cleanup:
2013 return ret;
2014}
2015
Michal Vasko6f865982023-11-21 12:10:42 +01002016/**
2017 * @brief Get UID of the owner of a socket.
2018 *
2019 * @param[in] sock Socket to analyze.
2020 * @param[out] uid Socket owner UID.
2021 * @return 0 on success,
2022 * @return -1 on error.
2023 */
Michal Vasko5f352c52019-07-10 16:12:06 +02002024static int
apropp-molex4e903c32020-04-20 03:06:58 -04002025nc_get_uid(int sock, uid_t *uid)
2026{
Michal Vasko6f865982023-11-21 12:10:42 +01002027 int r;
apropp-molex4e903c32020-04-20 03:06:58 -04002028
Michal Vaskod3910912020-04-20 09:12:49 +02002029#ifdef SO_PEERCRED
2030 struct ucred ucred;
2031 socklen_t len;
Michal Vasko292c5542023-02-01 14:33:17 +01002032
Michal Vaskod3910912020-04-20 09:12:49 +02002033 len = sizeof(ucred);
Michal Vasko6f865982023-11-21 12:10:42 +01002034 r = getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len);
2035 if (!r) {
Michal Vaskod3910912020-04-20 09:12:49 +02002036 *uid = ucred.uid;
2037 }
2038#else
Michal Vasko6f865982023-11-21 12:10:42 +01002039 r = getpeereid(sock, uid, NULL);
Michal Vaskod3910912020-04-20 09:12:49 +02002040#endif
2041
Michal Vasko6f865982023-11-21 12:10:42 +01002042 if (r < 0) {
2043 ERR(NULL, "Failed to get owner UID of a UNIX socket (%s).", strerror(errno));
Michal Vaskod3910912020-04-20 09:12:49 +02002044 return -1;
2045 }
apropp-molex4e903c32020-04-20 03:06:58 -04002046 return 0;
2047}
2048
2049static int
Michal Vasko5f352c52019-07-10 16:12:06 +02002050nc_accept_unix(struct nc_session *session, int sock)
2051{
Michal Vaskob83a3fa2021-05-26 09:53:42 +02002052#if defined (SO_PEERCRED) || defined (HAVE_GETPEEREID)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002053 struct passwd *pw, pw_buf;
Michal Vasko5f352c52019-07-10 16:12:06 +02002054 char *username;
Michal Vasko292c5542023-02-01 14:33:17 +01002055
Michal Vasko5f352c52019-07-10 16:12:06 +02002056 session->ti_type = NC_TI_UNIX;
Michal Vasko143aa142021-10-01 15:31:48 +02002057 uid_t uid = 0;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02002058 char *buf = NULL;
2059 size_t buf_len = 0;
Michal Vasko5f352c52019-07-10 16:12:06 +02002060
Michal Vaskod3910912020-04-20 09:12:49 +02002061 if (nc_get_uid(sock, &uid)) {
2062 close(sock);
Michal Vasko5f352c52019-07-10 16:12:06 +02002063 return -1;
2064 }
2065
romanf6e32012023-04-24 15:51:26 +02002066 pw = nc_getpw(uid, NULL, &pw_buf, &buf, &buf_len);
Michal Vasko5f352c52019-07-10 16:12:06 +02002067 if (pw == NULL) {
Michal Vasko05532772021-06-03 12:12:38 +02002068 ERR(NULL, "Failed to find username for uid=%u (%s).\n", uid, strerror(errno));
Michal Vasko5f352c52019-07-10 16:12:06 +02002069 close(sock);
2070 return -1;
2071 }
2072
2073 username = strdup(pw->pw_name);
Michal Vaskoccd2dd02021-10-11 09:13:01 +02002074 free(buf);
Michal Vasko5f352c52019-07-10 16:12:06 +02002075 if (username == NULL) {
2076 ERRMEM;
2077 close(sock);
2078 return -1;
2079 }
Michal Vasko93224072021-11-09 12:14:28 +01002080 session->username = username;
Michal Vasko5f352c52019-07-10 16:12:06 +02002081
2082 session->ti.unixsock.sock = sock;
2083
2084 return 1;
Claus Klein22091912020-01-20 13:45:47 +01002085#else
2086 return -1;
2087#endif
Michal Vasko5f352c52019-07-10 16:12:06 +02002088}
2089
Michal Vaskoe2713da2016-08-22 16:06:40 +02002090API int
romanfb3f7cf2023-11-30 16:10:09 +01002091nc_server_add_endpt_unix_socket_listen(const char *endpt_name, const char *unix_socket_path, mode_t mode, uid_t uid, gid_t gid)
2092{
2093 int ret = 0;
2094 void *tmp;
2095 uint16_t i;
2096
2097 NC_CHECK_ARG_RET(NULL, endpt_name, unix_socket_path, 1);
2098
2099 /* CONFIG LOCK */
2100 pthread_rwlock_wrlock(&server_opts.config_lock);
2101
2102 /* check name uniqueness */
2103 for (i = 0; i < server_opts.endpt_count; i++) {
2104 if (!strcmp(endpt_name, server_opts.endpts[i].name)) {
2105 ERR(NULL, "Endpoint \"%s\" already exists.", endpt_name);
2106 ret = 1;
2107 goto cleanup;
2108 }
2109 }
2110
2111 /* alloc a new endpoint */
2112 tmp = nc_realloc(server_opts.endpts, (server_opts.endpt_count + 1) * sizeof *server_opts.endpts);
2113 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2114 server_opts.endpts = tmp;
2115 memset(&server_opts.endpts[server_opts.endpt_count], 0, sizeof *server_opts.endpts);
2116
2117 /* alloc a new bind */
2118 tmp = nc_realloc(server_opts.binds, (server_opts.endpt_count + 1) * sizeof *server_opts.binds);
2119 NC_CHECK_ERRMEM_GOTO(!tmp, ret = 1, cleanup);
2120 server_opts.binds = tmp;
2121 memset(&server_opts.binds[server_opts.endpt_count], 0, sizeof *server_opts.binds);
2122 server_opts.binds[server_opts.endpt_count].sock = -1;
2123 server_opts.endpt_count++;
2124
2125 /* set name and ti */
2126 server_opts.endpts[server_opts.endpt_count - 1].name = strdup(endpt_name);
2127 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].name, ret = 1, cleanup);
2128 server_opts.endpts[server_opts.endpt_count - 1].ti = NC_TI_UNIX;
2129
2130 /* set the bind data */
2131 server_opts.binds[server_opts.endpt_count - 1].address = strdup(unix_socket_path);
2132 NC_CHECK_ERRMEM_GOTO(!server_opts.binds[server_opts.endpt_count - 1].address, ret = 1, cleanup);
2133
2134 /* alloc unix opts */
2135 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock = calloc(1, sizeof(struct nc_server_unix_opts));
2136 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock, ret = 1, cleanup);
2137
2138 /* set the opts data */
2139 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address = strdup(unix_socket_path);
2140 NC_CHECK_ERRMEM_GOTO(!server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->address, ret = 1, cleanup);
2141 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->mode = (mode == (mode_t) -1) ? (mode_t) -1 : mode;
2142 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->uid = (uid == (uid_t) -1) ? (uid_t) -1 : uid;
2143 server_opts.endpts[server_opts.endpt_count - 1].opts.unixsock->gid = (gid == (gid_t) -1) ? (gid_t) -1 : gid;
2144
2145 /* start listening */
2146 ret = nc_server_set_address_port(&server_opts.endpts[server_opts.endpt_count - 1],
2147 &server_opts.binds[server_opts.endpt_count - 1], NULL, 0);
2148 if (ret) {
2149 ERR(NULL, "Listening on UNIX socket \"%s\" failed.", unix_socket_path);
2150 goto cleanup;
2151 }
2152
2153cleanup:
2154 /* CONFIG UNLOCK */
2155 pthread_rwlock_unlock(&server_opts.config_lock);
2156 return ret;
2157}
2158
2159static void
2160nc_server_del_endpt_unix_socket_opts(struct nc_bind *bind, struct nc_server_unix_opts *opts)
2161{
2162 if (bind->sock > -1) {
2163 close(bind->sock);
2164 }
2165
2166 unlink(bind->address);
2167 free(bind->address);
2168 free(opts->address);
2169
2170 free(opts);
2171}
2172
2173void
2174_nc_server_del_endpt_unix_socket(struct nc_endpt *endpt, struct nc_bind *bind)
2175{
2176 free(endpt->name);
2177 nc_server_del_endpt_unix_socket_opts(bind, endpt->opts.unixsock);
2178
2179 server_opts.endpt_count--;
2180 if (!server_opts.endpt_count) {
2181 free(server_opts.endpts);
2182 free(server_opts.binds);
2183 server_opts.endpts = NULL;
2184 server_opts.binds = NULL;
2185 } else if (endpt != &server_opts.endpts[server_opts.endpt_count]) {
2186 memcpy(endpt, &server_opts.endpts[server_opts.endpt_count], sizeof *server_opts.endpts);
2187 memcpy(bind, &server_opts.binds[server_opts.endpt_count], sizeof *server_opts.binds);
2188 }
2189}
2190
2191API void
2192nc_server_del_endpt_unix_socket(const char *endpt_name)
2193{
2194 uint16_t i;
2195 struct nc_endpt *endpt = NULL;
2196 struct nc_bind *bind;
2197
2198 /* CONFIG LOCK */
2199 pthread_rwlock_wrlock(&server_opts.config_lock);
2200
2201 NC_CHECK_ARG_RET(NULL, endpt_name, );
2202
2203 for (i = 0; i < server_opts.endpt_count; i++) {
2204 if (!strcmp(server_opts.endpts[i].name, endpt_name)) {
2205 endpt = &server_opts.endpts[i];
2206 bind = &server_opts.binds[i];
2207 break;
2208 }
2209 }
2210 if (!endpt) {
2211 ERR(NULL, "Endpoint \"%s\" not found.", endpt_name);
2212 goto end;
2213 }
2214 if (endpt->ti != NC_TI_UNIX) {
2215 ERR(NULL, "Endpoint \"%s\" is not a UNIX socket endpoint.", endpt_name);
2216 goto end;
2217 }
2218
2219 _nc_server_del_endpt_unix_socket(endpt, bind);
2220
2221end:
2222 /* CONFIG UNLOCK */
2223 pthread_rwlock_unlock(&server_opts.config_lock);
2224}
2225
2226API int
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002227nc_server_endpt_count(void)
2228{
2229 return server_opts.endpt_count;
2230}
2231
Michal Vasko71090fc2016-05-24 16:37:28 +02002232API NC_MSG_TYPE
Michal Vasko93224072021-11-09 12:14:28 +01002233nc_accept(int timeout, const struct ly_ctx *ctx, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +01002234{
Michal Vasko71090fc2016-05-24 16:37:28 +02002235 NC_MSG_TYPE msgtype;
Michal Vasko1a38c862016-01-15 15:50:07 +01002236 int sock, ret;
Michal Vasko5c2f7952016-01-22 13:16:31 +01002237 char *host = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002238 uint16_t port, bind_idx;
Michal Vasko9fb42272017-10-05 13:50:05 +02002239 struct timespec ts_cur;
Michal Vasko9e036d52016-01-08 10:49:26 +01002240
romanf578cd52023-10-19 09:47:40 +02002241 NC_CHECK_ARG_RET(NULL, ctx, session, NC_MSG_ERROR);
Michal Vasko9e036d52016-01-08 10:49:26 +01002242
Michal Vasko93224072021-11-09 12:14:28 +01002243 /* init ctx as needed */
romanf578cd52023-10-19 09:47:40 +02002244 nc_server_init_cb_ctx(ctx);
Michal Vasko93224072021-11-09 12:14:28 +01002245
romanf578cd52023-10-19 09:47:40 +02002246 /* CONFIG LOCK */
2247 pthread_rwlock_rdlock(&server_opts.config_lock);
Michal Vasko51e514d2016-02-02 15:51:52 +01002248
2249 if (!server_opts.endpt_count) {
Michal Vasko05532772021-06-03 12:12:38 +02002250 ERR(NULL, "No endpoints to accept sessions on.");
romanf578cd52023-10-19 09:47:40 +02002251 /* CONFIG UNLOCK */
2252 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002253 return NC_MSG_ERROR;
Michal Vasko51e514d2016-02-02 15:51:52 +01002254 }
Michal Vaskob48aa812016-01-18 14:13:09 +01002255
romanf578cd52023-10-19 09:47:40 +02002256 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 +01002257 if (ret < 1) {
Michal Vaskob737d752016-02-09 09:01:27 +01002258 free(host);
romanf578cd52023-10-19 09:47:40 +02002259 /* CONFIG UNLOCK */
2260 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko5e203472016-05-30 15:27:58 +02002261 if (!ret) {
2262 return NC_MSG_WOULDBLOCK;
2263 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002264 return NC_MSG_ERROR;
Michal Vasko9e036d52016-01-08 10:49:26 +01002265 }
Michal Vaskoade892d2017-02-22 13:40:35 +01002266
Michal Vaskob48aa812016-01-18 14:13:09 +01002267 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +01002268
Michal Vasko131120a2018-05-29 15:44:02 +02002269 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002270 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(host); msgtype = NC_MSG_ERROR, cleanup);
Michal Vasko1a38c862016-01-15 15:50:07 +01002271 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002272 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vasko1a38c862016-01-15 15:50:07 +01002273 (*session)->flags = NC_SESSION_SHAREDCTX;
Michal Vasko93224072021-11-09 12:14:28 +01002274 (*session)->host = host;
Michal Vasko1a38c862016-01-15 15:50:07 +01002275 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +01002276
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002277 /* sock gets assigned to session or closed */
romanf578cd52023-10-19 09:47:40 +02002278#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002279 if (server_opts.endpts[bind_idx].ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002280 ret = nc_accept_ssh_session(*session, server_opts.endpts[bind_idx].opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko71090fc2016-05-24 16:37:28 +02002281 if (ret < 0) {
2282 msgtype = NC_MSG_ERROR;
2283 goto cleanup;
2284 } else if (!ret) {
2285 msgtype = NC_MSG_WOULDBLOCK;
2286 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002287 }
romanf578cd52023-10-19 09:47:40 +02002288 } else if (server_opts.endpts[bind_idx].ti == NC_TI_OPENSSL) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002289 (*session)->data = server_opts.endpts[bind_idx].opts.tls;
romanf578cd52023-10-19 09:47:40 +02002290 ret = nc_accept_tls_session(*session, server_opts.endpts[bind_idx].opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko71090fc2016-05-24 16:37:28 +02002291 if (ret < 0) {
2292 msgtype = NC_MSG_ERROR;
2293 goto cleanup;
2294 } else if (!ret) {
2295 msgtype = NC_MSG_WOULDBLOCK;
2296 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002297 }
Michal Vasko3d865d22016-01-28 16:00:53 +01002298 } else
romanf578cd52023-10-19 09:47:40 +02002299#endif /* NC_ENABLED_SSH_TLS */
Olivier Matzac7fa2f2018-10-11 10:02:04 +02002300 if (server_opts.endpts[bind_idx].ti == NC_TI_UNIX) {
2301 (*session)->data = server_opts.endpts[bind_idx].opts.unixsock;
2302 ret = nc_accept_unix(*session, sock);
2303 if (ret < 0) {
2304 msgtype = NC_MSG_ERROR;
2305 goto cleanup;
2306 }
2307 } else {
Michal Vasko9e036d52016-01-08 10:49:26 +01002308 ERRINT;
Michal Vaskoc14e3c82016-01-11 16:14:30 +01002309 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002310 msgtype = NC_MSG_ERROR;
2311 goto cleanup;
Michal Vasko9e036d52016-01-08 10:49:26 +01002312 }
2313
Michal Vasko2cc4c682016-03-01 09:16:48 +01002314 (*session)->data = NULL;
2315
romanf578cd52023-10-19 09:47:40 +02002316 /* CONFIG UNLOCK */
2317 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002318
Michal Vaskob48aa812016-01-18 14:13:09 +01002319 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002320 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob48aa812016-01-18 14:13:09 +01002321
Michal Vasko9e036d52016-01-08 10:49:26 +01002322 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002323 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002324 if (msgtype != NC_MSG_HELLO) {
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002325 nc_session_free(*session, NULL);
Michal Vasko3031aae2016-01-27 16:07:18 +01002326 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002327 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002328 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002329
Michal Vaskod8a74192023-02-06 15:51:50 +01002330 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002331 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002332 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002333 (*session)->opts.server.session_start = ts_cur;
Michal Vasko1a38c862016-01-15 15:50:07 +01002334 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +01002335
Michal Vasko71090fc2016-05-24 16:37:28 +02002336 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002337
Michal Vasko71090fc2016-05-24 16:37:28 +02002338cleanup:
romanf578cd52023-10-19 09:47:40 +02002339 /* CONFIG UNLOCK */
2340 pthread_rwlock_unlock(&server_opts.config_lock);
Michal Vasko3031aae2016-01-27 16:07:18 +01002341
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002342 nc_session_free(*session, NULL);
Michal Vasko1a38c862016-01-15 15:50:07 +01002343 *session = NULL;
Michal Vasko71090fc2016-05-24 16:37:28 +02002344 return msgtype;
Michal Vasko9e036d52016-01-08 10:49:26 +01002345}
2346
romanf578cd52023-10-19 09:47:40 +02002347#ifdef NC_ENABLED_SSH_TLS
Michal Vasko2e6defd2016-10-07 15:48:15 +02002348
2349API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002350nc_server_ch_is_client(const char *name)
2351{
2352 uint16_t i;
2353 int found = 0;
2354
2355 if (!name) {
2356 return found;
2357 }
2358
2359 /* READ LOCK */
2360 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2361
2362 /* check name uniqueness */
2363 for (i = 0; i < server_opts.ch_client_count; ++i) {
2364 if (!strcmp(server_opts.ch_clients[i].name, name)) {
2365 found = 1;
2366 break;
2367 }
2368 }
2369
2370 /* UNLOCK */
2371 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2372
2373 return found;
2374}
2375
2376API int
Michal Vaskofb1724b2020-01-31 11:02:00 +01002377nc_server_ch_client_is_endpt(const char *client_name, const char *endpt_name)
2378{
2379 uint16_t i;
2380 struct nc_ch_client *client = NULL;
2381 int found = 0;
2382
2383 if (!client_name || !endpt_name) {
2384 return found;
2385 }
2386
2387 /* READ LOCK */
2388 pthread_rwlock_rdlock(&server_opts.ch_client_lock);
2389
2390 for (i = 0; i < server_opts.ch_client_count; ++i) {
2391 if (!strcmp(server_opts.ch_clients[i].name, client_name)) {
2392 client = &server_opts.ch_clients[i];
2393 break;
2394 }
2395 }
2396
2397 if (!client) {
2398 goto cleanup;
2399 }
2400
2401 for (i = 0; i < client->ch_endpt_count; ++i) {
2402 if (!strcmp(client->ch_endpts[i].name, endpt_name)) {
2403 found = 1;
2404 break;
2405 }
2406 }
2407
2408cleanup:
2409 /* UNLOCK */
2410 pthread_rwlock_unlock(&server_opts.ch_client_lock);
2411 return found;
2412}
2413
Michal Vasko056f53c2022-10-21 13:38:15 +02002414/**
2415 * @brief Create a connection for an endpoint.
2416 *
2417 * Client lock is expected to be held.
2418 *
2419 * @param[in] endpt Endpoint to use.
2420 * @param[in] acquire_ctx_cb Callback for acquiring the libyang context.
2421 * @param[in] release_ctx_cb Callback for releasing the libyang context.
2422 * @param[in] ctx_cb_data Context callbacks data.
2423 * @param[out] session Created NC session.
2424 * @return NC_MSG values.
2425 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002426static NC_MSG_TYPE
Michal Vasko58bac1c2022-03-24 15:25:26 +01002427nc_connect_ch_endpt(struct nc_ch_endpt *endpt, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
2428 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 +01002429{
Michal Vasko71090fc2016-05-24 16:37:28 +02002430 NC_MSG_TYPE msgtype;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002431 const struct ly_ctx *ctx = NULL;
Michal Vaskob05053d2016-01-22 16:12:06 +01002432 int sock, ret;
Michal Vasko9fb42272017-10-05 13:50:05 +02002433 struct timespec ts_cur;
Michal Vasko66032bc2019-01-22 15:03:12 +01002434 char *ip_host;
Michal Vaskob05053d2016-01-22 16:12:06 +01002435
Michal Vasko056f53c2022-10-21 13:38:15 +02002436 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 +01002437 if (sock < 0) {
Michal Vasko71090fc2016-05-24 16:37:28 +02002438 return NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002439 }
2440
Michal Vasko93224072021-11-09 12:14:28 +01002441 /* acquire context */
2442 ctx = acquire_ctx_cb(ctx_cb_data);
2443 if (!ctx) {
2444 ERR(NULL, "Failed to acquire context for a new Call Home session.");
2445 close(sock);
2446 free(ip_host);
2447 return NC_MSG_ERROR;
2448 }
2449
romanf578cd52023-10-19 09:47:40 +02002450 /* init ctx as needed */
2451 nc_server_init_cb_ctx(ctx);
2452
Michal Vasko93224072021-11-09 12:14:28 +01002453 /* create session */
Michal Vasko131120a2018-05-29 15:44:02 +02002454 *session = nc_new_session(NC_SERVER, 0);
roman3a95bb22023-10-26 11:07:17 +02002455 NC_CHECK_ERRMEM_GOTO(!(*session), close(sock); free(ip_host); msgtype = NC_MSG_ERROR, fail);
Michal Vaskob05053d2016-01-22 16:12:06 +01002456 (*session)->status = NC_STATUS_STARTING;
Michal Vasko93224072021-11-09 12:14:28 +01002457 (*session)->ctx = (struct ly_ctx *)ctx;
Michal Vaskodc96bb92023-03-28 08:52:48 +02002458 (*session)->flags = NC_SESSION_SHAREDCTX | NC_SESSION_CALLHOME;
Michal Vasko93224072021-11-09 12:14:28 +01002459 (*session)->host = ip_host;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002460 (*session)->port = endpt->port;
Michal Vaskob05053d2016-01-22 16:12:06 +01002461
Michal Vaskob05053d2016-01-22 16:12:06 +01002462 /* sock gets assigned to session or closed */
Michal Vaskoadf30f02019-06-24 09:34:47 +02002463 if (endpt->ti == NC_TI_LIBSSH) {
romanf578cd52023-10-19 09:47:40 +02002464 ret = nc_accept_ssh_session(*session, endpt->opts.ssh, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002465 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002466
Michal Vasko71090fc2016-05-24 16:37:28 +02002467 if (ret < 0) {
2468 msgtype = NC_MSG_ERROR;
2469 goto fail;
2470 } else if (!ret) {
2471 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002472 goto fail;
2473 }
romanf578cd52023-10-19 09:47:40 +02002474 } else if (endpt->ti == NC_TI_OPENSSL) {
Michal Vaskoadf30f02019-06-24 09:34:47 +02002475 (*session)->data = endpt->opts.tls;
romanf578cd52023-10-19 09:47:40 +02002476 ret = nc_accept_tls_session(*session, endpt->opts.tls, sock, NC_TRANSPORT_TIMEOUT);
Michal Vasko2cc4c682016-03-01 09:16:48 +01002477 (*session)->data = NULL;
Michal Vaskoc6b9c7b2016-01-28 11:10:08 +01002478
Michal Vasko71090fc2016-05-24 16:37:28 +02002479 if (ret < 0) {
2480 msgtype = NC_MSG_ERROR;
2481 goto fail;
2482 } else if (!ret) {
2483 msgtype = NC_MSG_WOULDBLOCK;
Michal Vaskob05053d2016-01-22 16:12:06 +01002484 goto fail;
2485 }
roman423cc0d2023-11-24 11:29:47 +01002486 } else {
Michal Vaskob05053d2016-01-22 16:12:06 +01002487 ERRINT;
2488 close(sock);
Michal Vasko71090fc2016-05-24 16:37:28 +02002489 msgtype = NC_MSG_ERROR;
Michal Vaskob05053d2016-01-22 16:12:06 +01002490 goto fail;
2491 }
2492
2493 /* assign new SID atomically */
Michal Vasko5bd4a3f2021-06-17 16:40:10 +02002494 (*session)->id = ATOMIC_INC_RELAXED(server_opts.new_session_id);
Michal Vaskob05053d2016-01-22 16:12:06 +01002495
2496 /* NETCONF handshake */
Michal Vasko131120a2018-05-29 15:44:02 +02002497 msgtype = nc_handshake_io(*session);
Michal Vasko71090fc2016-05-24 16:37:28 +02002498 if (msgtype != NC_MSG_HELLO) {
Michal Vaskob05053d2016-01-22 16:12:06 +01002499 goto fail;
2500 }
Michal Vasko9fb42272017-10-05 13:50:05 +02002501
Michal Vaskod8a74192023-02-06 15:51:50 +01002502 nc_timeouttime_get(&ts_cur, 0);
Michal Vasko9fb42272017-10-05 13:50:05 +02002503 (*session)->opts.server.last_rpc = ts_cur.tv_sec;
Michal Vaskod8a74192023-02-06 15:51:50 +01002504 nc_realtime_get(&ts_cur);
roman44efa322023-11-03 13:57:25 +01002505 (*session)->opts.server.session_start = ts_cur;
Michal Vaskob05053d2016-01-22 16:12:06 +01002506 (*session)->status = NC_STATUS_RUNNING;
2507
Michal Vasko71090fc2016-05-24 16:37:28 +02002508 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002509
2510fail:
Michal Vaskoe1a64ec2016-03-01 12:21:58 +01002511 nc_session_free(*session, NULL);
Michal Vaskob05053d2016-01-22 16:12:06 +01002512 *session = NULL;
Michal Vasko58bac1c2022-03-24 15:25:26 +01002513 if (ctx) {
2514 release_ctx_cb(ctx_cb_data);
2515 }
Michal Vasko71090fc2016-05-24 16:37:28 +02002516 return msgtype;
Michal Vaskob05053d2016-01-22 16:12:06 +01002517}
2518
Michal Vasko6f865982023-11-21 12:10:42 +01002519/**
2520 * @brief Wait for any event after a NC session was established on a CH client.
2521 *
Michal Vasko6f865982023-11-21 12:10:42 +01002522 * @param[in] data CH client thread argument.
roman8341e8b2023-11-23 16:12:42 +01002523 * @param[in] session New NC session. The session is invalid upon being freed (= function exit).
Michal Vasko6f865982023-11-21 12:10:42 +01002524 * @return 0 if session was terminated normally,
2525 * @return 1 if the CH client was removed,
2526 * @return -1 on error.
2527 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002528static int
roman8341e8b2023-11-23 16:12:42 +01002529nc_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 +02002530{
Michal Vasko6f865982023-11-21 12:10:42 +01002531 int rc = 0, r;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002532 uint32_t idle_timeout;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002533 struct timespec ts;
2534 struct nc_ch_client *client;
2535
Michal Vasko2e6defd2016-10-07 15:48:15 +02002536 /* CH LOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002537 pthread_mutex_lock(&session->opts.server.ch_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002538
Michal Vaskofeccb312022-03-24 15:24:59 +01002539 session->flags |= NC_SESSION_CH_THREAD;
Michal Vasko0db3db52021-03-03 10:45:42 +01002540
Michal Vasko2e6defd2016-10-07 15:48:15 +02002541 /* give the session to the user */
romanf578cd52023-10-19 09:47:40 +02002542 if (data->new_session_cb(data->client_name, session, data->new_session_cb_data)) {
Michal Vaskof1c26c22021-04-12 16:34:33 +02002543 /* something is wrong, free the session */
Michal Vaskofeccb312022-03-24 15:24:59 +01002544 session->flags &= ~NC_SESSION_CH_THREAD;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002545
2546 /* CH UNLOCK */
2547 pthread_mutex_unlock(&session->opts.server.ch_lock);
2548
Michal Vasko77d56d72022-09-07 10:30:48 +02002549 /* session terminated, free it and release its context */
Michal Vaskof1c26c22021-04-12 16:34:33 +02002550 nc_session_free(session, NULL);
Michal Vasko58bac1c2022-03-24 15:25:26 +01002551 data->release_ctx_cb(data->ctx_cb_data);
Michal Vasko6f865982023-11-21 12:10:42 +01002552 return 0;
Michal Vaskof1c26c22021-04-12 16:34:33 +02002553 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002554
2555 do {
romanf578cd52023-10-19 09:47:40 +02002556 nc_timeouttime_get(&ts, NC_CH_THREAD_IDLE_TIMEOUT_SLEEP);
Michal Vasko6f865982023-11-21 12:10:42 +01002557
Michal Vasko0db3db52021-03-03 10:45:42 +01002558 /* CH COND WAIT */
Michal Vaskod8a74192023-02-06 15:51:50 +01002559 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 +01002560 if (!r) {
2561 /* we were woken up, something probably happened */
2562 if (session->status != NC_STATUS_RUNNING) {
2563 break;
2564 }
2565 } else if (r != ETIMEDOUT) {
Michal Vasko05532772021-06-03 12:12:38 +02002566 ERR(session, "Pthread condition timedwait failed (%s).", strerror(r));
Michal Vasko6f865982023-11-21 12:10:42 +01002567 rc = -1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002568 break;
Michal Vasko2e39ed92018-03-12 13:51:44 +01002569 }
2570
Michal Vasko2e6defd2016-10-07 15:48:15 +02002571 /* check whether the client was not removed */
Michal Vasko6f865982023-11-21 12:10:42 +01002572
Michal Vasko2e6defd2016-10-07 15:48:15 +02002573 /* LOCK */
Michal Vasko6f865982023-11-21 12:10:42 +01002574 client = nc_server_ch_client_lock(data->client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002575 if (!client) {
2576 /* client was removed, finish thread */
Michal Vasko05532772021-06-03 12:12:38 +02002577 VRB(session, "Call Home client \"%s\" removed, but an established session will not be terminated.",
Michal Vaskoadf30f02019-06-24 09:34:47 +02002578 data->client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002579 rc = 1;
Michal Vasko3f05a092018-03-13 10:39:49 +01002580 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002581 }
2582
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002583 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002584 idle_timeout = client->idle_timeout;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002585 } else {
2586 idle_timeout = 0;
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002587 }
2588
Michal Vaskod8a74192023-02-06 15:51:50 +01002589 nc_timeouttime_get(&ts, 0);
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002590 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 +02002591 VRB(session, "Call Home client \"%s\": session idle timeout elapsed.", client->name);
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002592 session->status = NC_STATUS_INVALID;
2593 session->term_reason = NC_SESSION_TERM_TIMEOUT;
2594 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002595
2596 /* UNLOCK */
2597 nc_server_ch_client_unlock(client);
2598
2599 } while (session->status == NC_STATUS_RUNNING);
2600
Michal Vaskofeccb312022-03-24 15:24:59 +01002601 /* signal to nc_session_free() that CH thread is terminating */
2602 session->flags &= ~NC_SESSION_CH_THREAD;
2603 pthread_cond_signal(&session->opts.server.ch_cond);
Michal Vasko0db3db52021-03-03 10:45:42 +01002604
Michal Vasko27377422018-03-15 08:59:35 +01002605 /* CH UNLOCK */
Michal Vaskoacf98472021-02-04 15:33:57 +01002606 pthread_mutex_unlock(&session->opts.server.ch_lock);
Michal Vasko27377422018-03-15 08:59:35 +01002607
Michal Vasko6f865982023-11-21 12:10:42 +01002608 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002609}
2610
romanf578cd52023-10-19 09:47:40 +02002611/**
2612 * @brief Waits for some amount of time while reacting to signals about terminating a Call Home thread.
2613 *
2614 * @param[in] session An established session.
2615 * @param[in] data Call Home thread's data.
2616 * @param[in] cond_wait_time Time in seconds to sleep for, after which a reconnect is attempted.
2617 *
2618 * @return 0 if the thread should stop running, 1 if it should continue.
2619 */
2620static int
2621nc_server_ch_client_thread_is_running_wait(struct nc_session *session, struct nc_ch_client_thread_arg *data, uint64_t cond_wait_time)
2622{
2623 struct timespec ts;
2624 int ret = 0, thread_running;
2625
2626 /* COND LOCK */
2627 pthread_mutex_lock(&data->cond_lock);
2628 /* get reconnect timeout in ms */
2629 nc_timeouttime_get(&ts, cond_wait_time * 1000);
2630 while (!ret && data->thread_running) {
2631 ret = pthread_cond_clockwait(&data->cond, &data->cond_lock, COMPAT_CLOCK_ID, &ts);
2632 }
2633
2634 thread_running = data->thread_running;
2635 /* COND UNLOCK */
2636 pthread_mutex_unlock(&data->cond_lock);
2637
2638 if (!thread_running) {
2639 /* thread is terminating */
2640 VRB(session, "Call Home thread signaled to exit, client \"%s\" probably removed.", data->client_name);
2641 ret = 0;
2642 } else if (ret == ETIMEDOUT) {
2643 /* time to reconnect */
2644 VRB(session, "Call Home client \"%s\" timeout of %" PRIu64 " seconds expired, reconnecting.", data->client_name, cond_wait_time);
2645 ret = 1;
2646 } else if (ret) {
2647 ERR(session, "Pthread condition timedwait failed (%s).", strerror(ret));
2648 ret = 0;
2649 }
2650
2651 return ret;
2652}
2653
2654/**
2655 * @brief Checks if a Call Home thread should terminate.
2656 *
2657 * Checks the shared boolean variable thread_running. This should be done everytime
2658 * before entering a critical section.
2659 *
2660 * @param[in] data Call Home thread's data.
2661 *
2662 * @return 0 if the thread should stop running, -1 if it can continue.
2663 */
2664static int
2665nc_server_ch_client_thread_is_running(struct nc_ch_client_thread_arg *data)
2666{
2667 int ret = -1;
2668
2669 /* COND LOCK */
2670 pthread_mutex_lock(&data->cond_lock);
2671 if (!data->thread_running) {
2672 /* thread should stop running */
2673 ret = 0;
2674 }
2675 /* COND UNLOCK */
2676 pthread_mutex_unlock(&data->cond_lock);
2677
2678 return ret;
2679}
2680
Michal Vasko6f865982023-11-21 12:10:42 +01002681/**
2682 * @brief Lock CH client structures for reading and lock the specific client if it has some endpoints, wait otherwise.
2683 *
2684 * @param[in] name Name of the CH client.
2685 * @return Pointer to the CH client.
2686 */
2687static struct nc_ch_client *
2688nc_server_ch_client_with_endpt_lock(const char *name)
2689{
2690 struct nc_ch_client *client;
2691
2692 while (1) {
2693 /* LOCK */
2694 client = nc_server_ch_client_lock(name);
2695 if (!client) {
2696 return NULL;
2697 }
2698 if (client->ch_endpt_count) {
2699 return client;
2700 }
2701 /* no endpoints defined yet */
2702
2703 /* UNLOCK */
2704 nc_server_ch_client_unlock(client);
2705
2706 usleep(NC_CH_NO_ENDPT_WAIT * 1000);
2707 }
2708
2709 return NULL;
2710}
2711
2712/**
2713 * @brief Call Home client management thread.
2714 *
2715 * @param[in] arg CH client thread argument.
2716 * @return NULL.
2717 */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002718static void *
2719nc_ch_client_thread(void *arg)
2720{
Michal Vasko6f865982023-11-21 12:10:42 +01002721 struct nc_ch_client_thread_arg *data = arg;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002722 NC_MSG_TYPE msgtype;
2723 uint8_t cur_attempts = 0;
romanf578cd52023-10-19 09:47:40 +02002724 uint16_t next_endpt_index, max_wait;
Michal Vasko9550cf12017-03-21 15:33:58 +01002725 char *cur_endpt_name = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002726 struct nc_ch_endpt *cur_endpt;
romanf578cd52023-10-19 09:47:40 +02002727 struct nc_session *session = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002728 struct nc_ch_client *client;
romanf578cd52023-10-19 09:47:40 +02002729 uint32_t reconnect_in;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002730
2731 /* LOCK */
2732 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002733 if (!client) {
2734 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2735 goto cleanup;
2736 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002737
2738 cur_endpt = &client->ch_endpts[0];
2739 cur_endpt_name = strdup(cur_endpt->name);
2740
Michal Vasko6f865982023-11-21 12:10:42 +01002741 while (nc_server_ch_client_thread_is_running(data)) {
Michal Vasko056f53c2022-10-21 13:38:15 +02002742 if (!cur_attempts) {
2743 VRB(NULL, "Call Home client \"%s\" endpoint \"%s\" connecting...", data->client_name, cur_endpt_name);
2744 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002745
Michal Vasko6f865982023-11-21 12:10:42 +01002746 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 +02002747 if (msgtype == NC_MSG_HELLO) {
2748 /* UNLOCK */
2749 nc_server_ch_client_unlock(client);
2750
romanf578cd52023-10-19 09:47:40 +02002751 if (!nc_server_ch_client_thread_is_running(data)) {
2752 /* thread should stop running */
2753 goto cleanup;
2754 }
2755
2756 /* run while the session is established */
2757 VRB(session, "Call Home client \"%s\" session %u established.", data->client_name, session->id);
roman8341e8b2023-11-23 16:12:42 +01002758 if (nc_server_ch_client_thread_session_cond_wait(data, session)) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002759 goto cleanup;
2760 }
roman8341e8b2023-11-23 16:12:42 +01002761 session = NULL;
romanf578cd52023-10-19 09:47:40 +02002762
roman8341e8b2023-11-23 16:12:42 +01002763 VRB(NULL, "Call Home client \"%s\" session terminated.", data->client_name);
romanf578cd52023-10-19 09:47:40 +02002764 if (!nc_server_ch_client_thread_is_running(data)) {
2765 /* thread should stop running */
2766 goto cleanup;
2767 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002768
2769 /* LOCK */
2770 client = nc_server_ch_client_with_endpt_lock(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002771 if (!client) {
2772 VRB(NULL, "Call Home client \"%s\" removed.", data->client_name);
2773 goto cleanup;
2774 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002775
2776 /* session changed status -> it was disconnected for whatever reason,
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002777 * persistent connection immediately tries to reconnect, periodic connects at specific times */
Michal Vasko2e6defd2016-10-07 15:48:15 +02002778 if (client->conn_type == NC_CH_PERIOD) {
romanf578cd52023-10-19 09:47:40 +02002779 if (client->anchor_time) {
Michal Vasko18e1fa02021-11-29 09:02:05 +01002780 /* anchored */
romanf578cd52023-10-19 09:47:40 +02002781 reconnect_in = (time(NULL) - client->anchor_time) % (client->period * 60);
Michal Vasko18e1fa02021-11-29 09:02:05 +01002782 } else {
2783 /* fixed timeout */
romanf578cd52023-10-19 09:47:40 +02002784 reconnect_in = client->period * 60;
Michal Vasko18e1fa02021-11-29 09:02:05 +01002785 }
2786
Michal Vasko2e6defd2016-10-07 15:48:15 +02002787 /* UNLOCK */
2788 nc_server_ch_client_unlock(client);
2789
romanf578cd52023-10-19 09:47:40 +02002790 /* wait for the timeout to elapse, so we can try to reconnect */
2791 VRB(session, "Call Home client \"%s\" reconnecting in %" PRIu32 " seconds.", data->client_name, reconnect_in);
2792 if (!nc_server_ch_client_thread_is_running_wait(session, data, reconnect_in)) {
2793 goto cleanup;
2794 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002795
2796 /* LOCK */
2797 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002798 assert(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002799 }
2800
2801 /* set next endpoint to try */
2802 if (client->start_with == NC_CH_FIRST_LISTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002803 next_endpt_index = 0;
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002804 } else if (client->start_with == NC_CH_LAST_CONNECTED) {
Peter Feiged05f2252018-09-03 08:09:47 +00002805 /* we keep the current one but due to unlock/lock we have to find it again */
2806 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2807 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
2808 break;
2809 }
2810 }
2811 if (next_endpt_index >= client->ch_endpt_count) {
2812 /* endpoint was removed, start with the first one */
2813 next_endpt_index = 0;
2814 }
Michal Vaskoe49a15f2019-05-27 14:18:36 +02002815 } else {
2816 /* just get a random index */
2817 next_endpt_index = rand() % client->ch_endpt_count;
Peter Feiged05f2252018-09-03 08:09:47 +00002818 }
2819
Michal Vasko2e6defd2016-10-07 15:48:15 +02002820 } else {
romanf578cd52023-10-19 09:47:40 +02002821 /* session was not created, wait a little bit and try again */
2822 max_wait = client->max_wait;
2823
Michal Vasko6bb116b2016-10-26 13:53:46 +02002824 /* UNLOCK */
2825 nc_server_ch_client_unlock(client);
2826
romanf578cd52023-10-19 09:47:40 +02002827 /* wait for max_wait seconds */
2828 if (!nc_server_ch_client_thread_is_running_wait(session, data, max_wait)) {
2829 /* thread should stop running */
2830 goto cleanup;
2831 }
Michal Vaskoc4bc5812016-10-13 10:59:36 +02002832
Michal Vasko6bb116b2016-10-26 13:53:46 +02002833 /* LOCK */
2834 client = nc_server_ch_client_with_endpt_lock(data->client_name);
romanf578cd52023-10-19 09:47:40 +02002835 assert(client);
Michal Vasko6bb116b2016-10-26 13:53:46 +02002836
Michal Vasko2e6defd2016-10-07 15:48:15 +02002837 ++cur_attempts;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002838
2839 /* try to find our endpoint again */
Peter Feiged05f2252018-09-03 08:09:47 +00002840 for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) {
2841 if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002842 break;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002843 }
Michal Vasko4cb8de52018-04-23 14:38:07 +02002844 }
2845
Peter Feiged05f2252018-09-03 08:09:47 +00002846 if (next_endpt_index >= client->ch_endpt_count) {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002847 /* endpoint was removed, start with the first one */
romanf578cd52023-10-19 09:47:40 +02002848 VRB(session, "Call Home client \"%s\" endpoint \"%s\" removed.", data->client_name, cur_endpt_name);
Peter Feiged05f2252018-09-03 08:09:47 +00002849 next_endpt_index = 0;
Michal Vasko4cb8de52018-04-23 14:38:07 +02002850 cur_attempts = 0;
2851 } else if (cur_attempts == client->max_attempts) {
2852 /* we have tried to connect to this endpoint enough times */
romanf578cd52023-10-19 09:47:40 +02002853 VRB(session, "Call Home client \"%s\" endpoint \"%s\" failed connection attempt limit %" PRIu8 " reached.",
Michal Vasko056f53c2022-10-21 13:38:15 +02002854 data->client_name, cur_endpt_name, client->max_attempts);
2855
2856 /* clear a pending socket, if any */
2857 cur_endpt = &client->ch_endpts[next_endpt_index];
2858 if (cur_endpt->sock_pending > -1) {
2859 close(cur_endpt->sock_pending);
2860 cur_endpt->sock_pending = -1;
2861 }
2862
Peter Feiged05f2252018-09-03 08:09:47 +00002863 if (next_endpt_index < client->ch_endpt_count - 1) {
Michal Vasko2e6defd2016-10-07 15:48:15 +02002864 /* just go to the next endpoint */
Peter Feiged05f2252018-09-03 08:09:47 +00002865 ++next_endpt_index;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002866 } else {
Michal Vasko4cb8de52018-04-23 14:38:07 +02002867 /* cur_endpoint is the last, start with the first one */
Michal Vasko2a225342018-09-05 08:38:34 +02002868 next_endpt_index = 0;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002869 }
Michal Vasko2e6defd2016-10-07 15:48:15 +02002870 cur_attempts = 0;
2871 } /* else we keep the current one */
2872 }
Peter Feiged05f2252018-09-03 08:09:47 +00002873
2874 cur_endpt = &client->ch_endpts[next_endpt_index];
2875 free(cur_endpt_name);
2876 cur_endpt_name = strdup(cur_endpt->name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002877 }
Michal Vasko6f865982023-11-21 12:10:42 +01002878
romanf578cd52023-10-19 09:47:40 +02002879 /* UNLOCK if we break out of the loop */
2880 nc_server_ch_client_unlock(client);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002881
2882cleanup:
romanf578cd52023-10-19 09:47:40 +02002883 VRB(session, "Call Home client \"%s\" thread exit.", data->client_name);
Michal Vasko9550cf12017-03-21 15:33:58 +01002884 free(cur_endpt_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002885 free(data->client_name);
roman8341e8b2023-11-23 16:12:42 +01002886 pthread_cond_destroy(&data->cond);
2887 pthread_mutex_destroy(&data->cond_lock);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002888 free(data);
2889 return NULL;
2890}
2891
2892API int
Michal Vasko93224072021-11-09 12:14:28 +01002893nc_connect_ch_client_dispatch(const char *client_name, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb,
romanf578cd52023-10-19 09:47:40 +02002894 nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb,
2895 void *new_session_cb_data)
Michal Vasko3f05a092018-03-13 10:39:49 +01002896{
Michal Vasko6f865982023-11-21 12:10:42 +01002897 int rc = 0, r;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002898 pthread_t tid;
Michal Vasko6f865982023-11-21 12:10:42 +01002899 struct nc_ch_client_thread_arg *arg = NULL;
romanf578cd52023-10-19 09:47:40 +02002900 struct nc_ch_client *ch_client;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002901
romanf578cd52023-10-19 09:47:40 +02002902 NC_CHECK_ARG_RET(NULL, client_name, acquire_ctx_cb, release_ctx_cb, new_session_cb, -1);
2903
Michal Vasko6f865982023-11-21 12:10:42 +01002904 /* LOCK */
2905 ch_client = nc_server_ch_client_lock(client_name);
2906 if (!ch_client) {
romanf578cd52023-10-19 09:47:40 +02002907 ERR(NULL, "Client \"%s\" not found.", client_name);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002908 return -1;
2909 }
2910
Michal Vasko6f865982023-11-21 12:10:42 +01002911 /* create the thread argument */
2912 arg = calloc(1, sizeof *arg);
2913 NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002914 arg->client_name = strdup(client_name);
Michal Vasko6f865982023-11-21 12:10:42 +01002915 NC_CHECK_ERRMEM_GOTO(!arg->client_name, rc = -1, cleanup);
Michal Vasko93224072021-11-09 12:14:28 +01002916 arg->acquire_ctx_cb = acquire_ctx_cb;
2917 arg->release_ctx_cb = release_ctx_cb;
2918 arg->ctx_cb_data = ctx_cb_data;
2919 arg->new_session_cb = new_session_cb;
romanf578cd52023-10-19 09:47:40 +02002920 arg->new_session_cb_data = new_session_cb_data;
romanf578cd52023-10-19 09:47:40 +02002921 pthread_cond_init(&arg->cond, NULL);
romanf578cd52023-10-19 09:47:40 +02002922 pthread_mutex_init(&arg->cond_lock, NULL);
Michal Vasko2e6defd2016-10-07 15:48:15 +02002923
Michal Vasko6f865982023-11-21 12:10:42 +01002924 /* creating the thread */
2925 arg->thread_running = 1;
2926 if ((r = pthread_create(&tid, NULL, nc_ch_client_thread, arg))) {
2927 ERR(NULL, "Creating a new thread failed (%s).", strerror(r));
2928 rc = -1;
2929 goto cleanup;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002930 }
Michal Vasko6f865982023-11-21 12:10:42 +01002931
Michal Vasko2e6defd2016-10-07 15:48:15 +02002932 /* the thread now manages arg */
romanf578cd52023-10-19 09:47:40 +02002933 ch_client->tid = tid;
2934 ch_client->thread_data = arg;
Michal Vasko6f865982023-11-21 12:10:42 +01002935 arg = NULL;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002936
Michal Vasko6f865982023-11-21 12:10:42 +01002937cleanup:
2938 /* UNLOCK */
2939 nc_server_ch_client_unlock(ch_client);
2940
2941 if (arg) {
2942 free(arg->client_name);
2943 free(arg);
2944 }
2945 return rc;
Michal Vasko2e6defd2016-10-07 15:48:15 +02002946}
2947
romanf578cd52023-10-19 09:47:40 +02002948#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof8352352016-05-24 09:11:36 +02002949
roman44efa322023-11-03 13:57:25 +01002950API struct timespec
Michal Vaskoc45ebd32016-05-25 11:17:36 +02002951nc_session_get_start_time(const struct nc_session *session)
Michal Vaskof8352352016-05-24 09:11:36 +02002952{
roman44efa322023-11-03 13:57:25 +01002953 struct timespec fail = {0};
2954
2955 NC_CHECK_ARG_RET(session, session, fail);
romanf578cd52023-10-19 09:47:40 +02002956
2957 if (session->side != NC_SERVER) {
2958 ERRARG(session, "session");
roman44efa322023-11-03 13:57:25 +01002959 return fail;
Michal Vaskof8352352016-05-24 09:11:36 +02002960 }
2961
Michal Vasko2e6defd2016-10-07 15:48:15 +02002962 return session->opts.server.session_start;
Michal Vaskof8352352016-05-24 09:11:36 +02002963}
Michal Vasko3486a7c2017-03-03 13:28:07 +01002964
2965API void
Michal Vasko71dbd772021-03-23 14:08:37 +01002966nc_session_inc_notif_status(struct nc_session *session)
Michal Vasko3486a7c2017-03-03 13:28:07 +01002967{
2968 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002969 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01002970 return;
2971 }
2972
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002973 /* NTF STATUS LOCK */
2974 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
2975
Michal Vasko71dbd772021-03-23 14:08:37 +01002976 ++session->opts.server.ntf_status;
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002977
2978 /* NTF STATUS UNLOCK */
2979 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko71dbd772021-03-23 14:08:37 +01002980}
2981
2982API void
2983nc_session_dec_notif_status(struct nc_session *session)
2984{
2985 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02002986 ERRARG(session, "session");
Michal Vasko71dbd772021-03-23 14:08:37 +01002987 return;
2988 }
2989
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002990 /* NTF STATUS LOCK */
2991 pthread_mutex_lock(&session->opts.server.ntf_status_lock);
2992
Michal Vasko71dbd772021-03-23 14:08:37 +01002993 if (session->opts.server.ntf_status) {
2994 --session->opts.server.ntf_status;
2995 }
Michal Vaskodf68e7e2022-04-21 11:04:00 +02002996
2997 /* NTF STATUS UNLOCK */
2998 pthread_mutex_unlock(&session->opts.server.ntf_status_lock);
Michal Vasko3486a7c2017-03-03 13:28:07 +01002999}
3000
3001API int
3002nc_session_get_notif_status(const struct nc_session *session)
3003{
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003004 uint32_t ntf_status;
3005
Michal Vasko3486a7c2017-03-03 13:28:07 +01003006 if (!session || (session->side != NC_SERVER)) {
romanf578cd52023-10-19 09:47:40 +02003007 ERRARG(session, "session");
Michal Vasko3486a7c2017-03-03 13:28:07 +01003008 return 0;
3009 }
3010
Michal Vaskodf68e7e2022-04-21 11:04:00 +02003011 /* NTF STATUS LOCK */
3012 pthread_mutex_lock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3013
3014 ntf_status = session->opts.server.ntf_status;
3015
3016 /* NTF STATUS UNLOCK */
3017 pthread_mutex_unlock(&((struct nc_session *)session)->opts.server.ntf_status_lock);
3018
3019 return ntf_status;
Michal Vasko086311b2016-01-08 09:53:11 +01003020}