blob: e1d6087c3d3e3c85ff984cdb9c4e53a8f475ebd9 [file] [log] [blame]
Radek Krejci206fcd62015-10-07 15:42:48 +02001/**
Michal Vasko95ea9ff2021-11-09 12:29:14 +01002 * @file io.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
Michal Vaskoa82e1a12024-05-13 09:43:19 +02004 * @author Michal Vasko <mvasko@cesnet.cz>
Michal Vasko95ea9ff2021-11-09 12:29:14 +01005 * @brief libnetconf2 - input/output functions
Radek Krejci206fcd62015-10-07 15:42:48 +02006 *
Michal Vasko95ea9ff2021-11-09 12:29:14 +01007 * @copyright
Michal Vaskoa82e1a12024-05-13 09:43:19 +02008 * Copyright (c) 2015 - 2024 CESNET, z.s.p.o.
Radek Krejci206fcd62015-10-07 15:42:48 +02009 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010010 * This source code is licensed under BSD 3-Clause License (the "License").
11 * You may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
Michal Vaskoafd416b2016-02-25 14:51:46 +010013 *
Radek Krejci9b81f5b2016-02-24 13:14:49 +010014 * https://opensource.org/licenses/BSD-3-Clause
Radek Krejci206fcd62015-10-07 15:42:48 +020015 */
16
Miroslav Mareš9563b812017-08-19 17:45:36 +020017#define _GNU_SOURCE /* asprintf, signals */
Michal Vaskoba9f3582023-02-22 10:26:32 +010018
Radek Krejci206fcd62015-10-07 15:42:48 +020019#include <assert.h>
20#include <errno.h>
Michal Vasko3512e402016-01-28 16:22:34 +010021#include <inttypes.h>
Michal Vasko976d11a2024-08-14 09:52:28 +020022#include <limits.h>
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +020023#include <pwd.h>
Radek Krejcife0b3472015-10-12 13:43:42 +020024#include <stdarg.h>
roman3f9b65c2023-06-05 14:26:58 +020025#include <stdint.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020026#include <stdlib.h>
27#include <string.h>
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +020028#include <sys/types.h>
Michal Vasko36c7be82017-02-22 13:37:59 +010029#include <time.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020030#include <unistd.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020031
32#include <libyang/libyang.h>
33
romana2481092024-04-05 12:30:22 +020034#include "compat.h"
roman008cfe72024-04-05 12:36:18 +020035#include "config.h"
roman3f9b65c2023-06-05 14:26:58 +020036#include "log_p.h"
37#include "messages_p.h"
38#include "netconf.h"
39#include "session.h"
40#include "session_p.h"
roman0fa69ee2024-04-23 15:11:02 +020041#include "session_wrapper.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020042
Michal Vasko8fe604c2020-02-10 15:25:04 +010043const char *nc_msgtype2str[] = {
44 "error",
45 "would block",
46 "no message",
47 "hello message",
48 "bad hello message",
49 "RPC message",
50 "rpc-reply message",
51 "rpc-reply message with wrong ID",
52 "notification message",
53};
54
Radek Krejcife0b3472015-10-12 13:43:42 +020055#define BUFFERSIZE 512
Radek Krejci206fcd62015-10-07 15:42:48 +020056
57static ssize_t
Michal Vasko3a8654b2024-05-13 09:43:49 +020058nc_read(struct nc_session *session, char *buf, uint32_t count, uint32_t inact_timeout, struct timespec *ts_act_timeout)
Radek Krejci206fcd62015-10-07 15:42:48 +020059{
Michal Vasko3a8654b2024-05-13 09:43:49 +020060 uint32_t readd = 0;
Michal Vasko9d8bee62016-03-03 10:58:24 +010061 ssize_t r = -1;
Robin Jarry7de4b8e2019-10-14 21:46:00 +020062 int fd, interrupted;
roman6ece9c52022-06-22 09:29:17 +020063 struct timespec ts_inact_timeout;
Radek Krejci206fcd62015-10-07 15:42:48 +020064
65 assert(session);
66 assert(buf);
67
Michal Vasko428087d2016-01-14 16:04:28 +010068 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
69 return -1;
70 }
71
Radek Krejci206fcd62015-10-07 15:42:48 +020072 if (!count) {
73 return 0;
74 }
75
Michal Vaskod8a74192023-02-06 15:51:50 +010076 nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
Michal Vasko81b33fb2016-09-26 14:57:36 +020077 do {
Robin Jarry7de4b8e2019-10-14 21:46:00 +020078 interrupted = 0;
Michal Vasko6b7c42e2016-03-02 15:46:41 +010079 switch (session->ti_type) {
80 case NC_TI_NONE:
81 return 0;
Michal Vasko38a7c6c2015-12-04 12:29:20 +010082
Michal Vasko6b7c42e2016-03-02 15:46:41 +010083 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +020084 case NC_TI_UNIX:
85 fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vasko6b7c42e2016-03-02 15:46:41 +010086 /* read via standard file descriptor */
Olivier Matzac7fa2f2018-10-11 10:02:04 +020087 r = read(fd, buf + readd, count - readd);
Radek Krejci206fcd62015-10-07 15:42:48 +020088 if (r < 0) {
Robin Jarry7de4b8e2019-10-14 21:46:00 +020089 if (errno == EAGAIN) {
Michal Vasko6b7c42e2016-03-02 15:46:41 +010090 r = 0;
91 break;
Robin Jarry7de4b8e2019-10-14 21:46:00 +020092 } else if (errno == EINTR) {
93 r = 0;
94 interrupted = 1;
95 break;
Radek Krejci206fcd62015-10-07 15:42:48 +020096 } else {
Michal Vasko05532772021-06-03 12:12:38 +020097 ERR(session, "Reading from file descriptor (%d) failed (%s).", fd, strerror(errno));
Michal Vasko428087d2016-01-14 16:04:28 +010098 session->status = NC_STATUS_INVALID;
99 session->term_reason = NC_SESSION_TERM_OTHER;
Radek Krejci206fcd62015-10-07 15:42:48 +0200100 return -1;
101 }
102 } else if (r == 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200103 ERR(session, "Communication file descriptor (%d) unexpectedly closed.", fd);
Michal Vasko428087d2016-01-14 16:04:28 +0100104 session->status = NC_STATUS_INVALID;
105 session->term_reason = NC_SESSION_TERM_DROPPED;
Radek Krejci206fcd62015-10-07 15:42:48 +0200106 return -1;
107 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100108 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200109
roman2eab4742023-06-06 10:00:26 +0200110#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200111 case NC_TI_SSH:
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100112 /* read via libssh */
Michal Vasko81b33fb2016-09-26 14:57:36 +0200113 r = ssh_channel_read(session->ti.libssh.channel, buf + readd, count - readd, 0);
Radek Krejci206fcd62015-10-07 15:42:48 +0200114 if (r == SSH_AGAIN) {
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100115 r = 0;
116 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200117 } else if (r == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +0200118 ERR(session, "Reading from the SSH channel failed (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko428087d2016-01-14 16:04:28 +0100119 session->status = NC_STATUS_INVALID;
120 session->term_reason = NC_SESSION_TERM_OTHER;
Radek Krejci206fcd62015-10-07 15:42:48 +0200121 return -1;
122 } else if (r == 0) {
123 if (ssh_channel_is_eof(session->ti.libssh.channel)) {
Michal Vasko05532772021-06-03 12:12:38 +0200124 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko428087d2016-01-14 16:04:28 +0100125 session->status = NC_STATUS_INVALID;
126 session->term_reason = NC_SESSION_TERM_DROPPED;
Radek Krejci206fcd62015-10-07 15:42:48 +0200127 return -1;
128 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100129 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200130 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100131 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200132
roman506354a2024-04-11 09:37:22 +0200133 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200134 r = nc_tls_read_wrap(session, (unsigned char *)buf + readd, count - readd);
135 if (r < 0) {
136 /* non-recoverable error */
137 return r;
Radek Krejci206fcd62015-10-07 15:42:48 +0200138 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100139 break;
roman2eab4742023-06-06 10:00:26 +0200140#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100141 }
142
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100143 if (r == 0) {
Michal Vaskof471fa02017-02-15 10:48:12 +0100144 /* nothing read */
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200145 if (!interrupted) {
146 usleep(NC_TIMEOUT_STEP);
147 }
Michal Vaskod8a74192023-02-06 15:51:50 +0100148 if ((nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) || (nc_timeouttime_cur_diff(ts_act_timeout) < 1)) {
149 if (nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200150 ERR(session, "Inactive read timeout elapsed.");
Michal Vaskof471fa02017-02-15 10:48:12 +0100151 } else {
Michal Vasko05532772021-06-03 12:12:38 +0200152 ERR(session, "Active read timeout elapsed.");
Michal Vaskof471fa02017-02-15 10:48:12 +0100153 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100154 session->status = NC_STATUS_INVALID;
155 session->term_reason = NC_SESSION_TERM_OTHER;
156 return -1;
157 }
Michal Vaskof471fa02017-02-15 10:48:12 +0100158 } else {
159 /* something read */
160 readd += r;
Michal Vasko36c7be82017-02-22 13:37:59 +0100161
162 /* reset inactive timeout */
Michal Vaskod8a74192023-02-06 15:51:50 +0100163 nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100164 }
165
Michal Vasko81b33fb2016-09-26 14:57:36 +0200166 } while (readd < count);
167 buf[count] = '\0';
Radek Krejci206fcd62015-10-07 15:42:48 +0200168
Michal Vasko81b33fb2016-09-26 14:57:36 +0200169 return (ssize_t)readd;
Radek Krejci206fcd62015-10-07 15:42:48 +0200170}
171
172static ssize_t
Michal Vasko36c7be82017-02-22 13:37:59 +0100173nc_read_chunk(struct nc_session *session, size_t len, uint32_t inact_timeout, struct timespec *ts_act_timeout, char **chunk)
Radek Krejci206fcd62015-10-07 15:42:48 +0200174{
175 ssize_t r;
176
177 assert(session);
178 assert(chunk);
179
180 if (!len) {
181 return 0;
182 }
183
Michal Vasko4eb3c312016-03-01 14:09:37 +0100184 *chunk = malloc((len + 1) * sizeof **chunk);
roman3a95bb22023-10-26 11:07:17 +0200185 NC_CHECK_ERRMEM_RET(!*chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200186
Michal Vasko36c7be82017-02-22 13:37:59 +0100187 r = nc_read(session, *chunk, len, inact_timeout, ts_act_timeout);
Radek Krejci206fcd62015-10-07 15:42:48 +0200188 if (r <= 0) {
189 free(*chunk);
190 return -1;
191 }
192
193 /* terminating null byte */
Radek Krejcife0b3472015-10-12 13:43:42 +0200194 (*chunk)[r] = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200195
196 return r;
197}
198
199static ssize_t
Michal Vasko36c7be82017-02-22 13:37:59 +0100200nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint32_t inact_timeout,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200201 struct timespec *ts_act_timeout, char **result)
Radek Krejci206fcd62015-10-07 15:42:48 +0200202{
203 char *chunk = NULL;
David Sedlákfedbc792018-07-04 11:07:07 +0200204 size_t size, count = 0, r, len, i, matched = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200205
206 assert(session);
207 assert(endtag);
208
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200209 if (limit && (limit < BUFFERSIZE)) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200210 size = limit;
211 } else {
212 size = BUFFERSIZE;
213 }
Michal Vasko4eb3c312016-03-01 14:09:37 +0100214 chunk = malloc((size + 1) * sizeof *chunk);
roman3a95bb22023-10-26 11:07:17 +0200215 NC_CHECK_ERRMEM_RET(!chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200216
217 len = strlen(endtag);
Michal Vasko428087d2016-01-14 16:04:28 +0100218 while (1) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200219 if (limit && (count == limit)) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200220 free(chunk);
Michal Vasko05532772021-06-03 12:12:38 +0200221 WRN(session, "Reading limit (%d) reached.", limit);
222 ERR(session, "Invalid input data (missing \"%s\" sequence).", endtag);
Radek Krejci206fcd62015-10-07 15:42:48 +0200223 return -1;
224 }
225
226 /* resize buffer if needed */
David Sedlákfedbc792018-07-04 11:07:07 +0200227 if ((count + (len - matched)) >= size) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200228 /* get more memory */
229 size = size + BUFFERSIZE;
Radek Krejcif6d9aef2018-08-17 11:50:53 +0200230 chunk = nc_realloc(chunk, (size + 1) * sizeof *chunk);
roman3a95bb22023-10-26 11:07:17 +0200231 NC_CHECK_ERRMEM_RET(!chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200232 }
233
234 /* get another character */
David Sedlákfedbc792018-07-04 11:07:07 +0200235 r = nc_read(session, &(chunk[count]), len - matched, inact_timeout, ts_act_timeout);
236 if (r != len - matched) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200237 free(chunk);
238 return -1;
239 }
240
David Sedlákfedbc792018-07-04 11:07:07 +0200241 count += len - matched;
Radek Krejci206fcd62015-10-07 15:42:48 +0200242
David Sedlákfedbc792018-07-04 11:07:07 +0200243 for (i = len - matched; i > 0; i--) {
244 if (!strncmp(&endtag[matched], &(chunk[count - i]), i)) {
245 /*part of endtag found */
246 matched += i;
Radek Krejci206fcd62015-10-07 15:42:48 +0200247 break;
David Sedlákfedbc792018-07-04 11:07:07 +0200248 } else {
249 matched = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200250 }
251 }
David Sedlákfedbc792018-07-04 11:07:07 +0200252
253 /* whole endtag found */
254 if (matched == len) {
255 break;
256 }
Radek Krejci206fcd62015-10-07 15:42:48 +0200257 }
258
259 /* terminating null byte */
260 chunk[count] = 0;
261
262 if (result) {
263 *result = chunk;
Radek Krejcife0b3472015-10-12 13:43:42 +0200264 } else {
265 free(chunk);
Radek Krejci206fcd62015-10-07 15:42:48 +0200266 }
267 return count;
268}
269
Michal Vasko77367452021-02-16 16:32:18 +0100270int
271nc_read_msg_io(struct nc_session *session, int io_timeout, struct ly_in **msg, int passing_io_lock)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100272{
Michal Vasko77367452021-02-16 16:32:18 +0100273 int ret = 1, r, io_locked = passing_io_lock;
274 char *data = NULL, *chunk;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100275 uint64_t chunk_len, len = 0;
Michal Vasko36c7be82017-02-22 13:37:59 +0100276 /* use timeout in milliseconds instead seconds */
277 uint32_t inact_timeout = NC_READ_INACT_TIMEOUT * 1000;
278 struct timespec ts_act_timeout;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100279
Michal Vasko77367452021-02-16 16:32:18 +0100280 assert(session && msg);
281 *msg = NULL;
Michal Vasko428087d2016-01-14 16:04:28 +0100282
283 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200284 ERR(session, "Invalid session to read from.");
Michal Vasko77367452021-02-16 16:32:18 +0100285 ret = -1;
Michal Vasko131120a2018-05-29 15:44:02 +0200286 goto cleanup;
Michal Vasko428087d2016-01-14 16:04:28 +0100287 }
288
Michal Vaskod8a74192023-02-06 15:51:50 +0100289 nc_timeouttime_get(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +0100290
Michal Vasko131120a2018-05-29 15:44:02 +0200291 if (!io_locked) {
292 /* SESSION IO LOCK */
293 ret = nc_session_io_lock(session, io_timeout, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100294 if (ret < 1) {
Michal Vasko131120a2018-05-29 15:44:02 +0200295 goto cleanup;
296 }
297 io_locked = 1;
298 }
299
Michal Vasko05ba9df2016-01-13 14:40:27 +0100300 /* read the message */
301 switch (session->version) {
302 case NC_VERSION_10:
Michal Vasko77367452021-02-16 16:32:18 +0100303 r = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, inact_timeout, &ts_act_timeout, &data);
304 if (r == -1) {
305 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200306 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100307 }
308
309 /* cut off the end tag */
Michal Vasko77367452021-02-16 16:32:18 +0100310 data[r - NC_VERSION_10_ENDTAG_LEN] = '\0';
Michal Vasko05ba9df2016-01-13 14:40:27 +0100311 break;
312 case NC_VERSION_11:
313 while (1) {
Michal Vasko77367452021-02-16 16:32:18 +0100314 r = nc_read_until(session, "\n#", 0, inact_timeout, &ts_act_timeout, NULL);
315 if (r == -1) {
316 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200317 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100318 }
Michal Vasko77367452021-02-16 16:32:18 +0100319 r = nc_read_until(session, "\n", 0, inact_timeout, &ts_act_timeout, &chunk);
320 if (r == -1) {
321 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200322 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100323 }
324
325 if (!strcmp(chunk, "#\n")) {
326 /* end of chunked framing message */
327 free(chunk);
Michal Vasko77367452021-02-16 16:32:18 +0100328 if (!data) {
Michal Vasko05532772021-06-03 12:12:38 +0200329 ERR(session, "Invalid frame chunk delimiters.");
Michal Vasko77367452021-02-16 16:32:18 +0100330 ret = -2;
331 goto cleanup;
Michal Vasko79df3262016-07-13 13:42:17 +0200332 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100333 break;
334 }
335
336 /* convert string to the size of the following chunk */
337 chunk_len = strtoul(chunk, (char **)NULL, 10);
338 free(chunk);
339 if (!chunk_len) {
Michal Vasko05532772021-06-03 12:12:38 +0200340 ERR(session, "Invalid frame chunk size detected, fatal error.");
Michal Vasko77367452021-02-16 16:32:18 +0100341 ret = -2;
342 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100343 }
344
345 /* now we have size of next chunk, so read the chunk */
Michal Vasko77367452021-02-16 16:32:18 +0100346 r = nc_read_chunk(session, chunk_len, inact_timeout, &ts_act_timeout, &chunk);
347 if (r == -1) {
348 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200349 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100350 }
351
352 /* realloc message buffer, remember to count terminating null byte */
Michal Vasko77367452021-02-16 16:32:18 +0100353 data = nc_realloc(data, len + chunk_len + 1);
roman3a95bb22023-10-26 11:07:17 +0200354 NC_CHECK_ERRMEM_GOTO(!data, ret = -1, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +0100355 memcpy(data + len, chunk, chunk_len);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100356 len += chunk_len;
Michal Vasko77367452021-02-16 16:32:18 +0100357 data[len] = '\0';
Michal Vasko05ba9df2016-01-13 14:40:27 +0100358 free(chunk);
359 }
360
361 break;
362 }
Michal Vasko131120a2018-05-29 15:44:02 +0200363
364 /* SESSION IO UNLOCK */
365 assert(io_locked);
366 nc_session_io_unlock(session, __func__);
367 io_locked = 0;
368
Michal Vasko05532772021-06-03 12:12:38 +0200369 DBG(session, "Received message:\n%s\n", data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100370
Michal Vasko77367452021-02-16 16:32:18 +0100371 /* build an input structure, eats data */
372 if (ly_in_new_memory(data, msg)) {
373 ret = -1;
374 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100375 }
Michal Vasko77367452021-02-16 16:32:18 +0100376 data = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100377
Michal Vasko131120a2018-05-29 15:44:02 +0200378cleanup:
379 if (io_locked) {
Michal Vasko77367452021-02-16 16:32:18 +0100380 /* SESSION IO UNLOCK */
Michal Vasko131120a2018-05-29 15:44:02 +0200381 nc_session_io_unlock(session, __func__);
382 }
Michal Vasko77367452021-02-16 16:32:18 +0100383 free(data);
Michal Vasko131120a2018-05-29 15:44:02 +0200384 return ret;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100385}
386
Michal Vasko428087d2016-01-14 16:04:28 +0100387/* return -1 means either poll error or that session was invalidated (socket error), EINTR is handled inside */
388static int
Michal Vasko131120a2018-05-29 15:44:02 +0200389nc_read_poll(struct nc_session *session, int io_timeout)
Michal Vasko428087d2016-01-14 16:04:28 +0100390{
391 int ret = -2;
392 struct pollfd fds;
Michal Vasko428087d2016-01-14 16:04:28 +0100393
394 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200395 ERR(session, "Invalid session to poll.");
Michal Vasko428087d2016-01-14 16:04:28 +0100396 return -1;
397 }
398
399 switch (session->ti_type) {
roman2eab4742023-06-06 10:00:26 +0200400#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200401 case NC_TI_SSH:
Michal Vasko976d11a2024-08-14 09:52:28 +0200402 if (io_timeout == -1) {
403 /* BUG libssh 0.11.0 replaces timeout -1 with 0 for non-blocking sessions */
404 io_timeout = INT_MAX;
405 }
406
Michal Vasko428087d2016-01-14 16:04:28 +0100407 /* EINTR is handled, it resumes waiting */
Michal Vasko131120a2018-05-29 15:44:02 +0200408 ret = ssh_channel_poll_timeout(session->ti.libssh.channel, io_timeout, 0);
Michal Vasko428087d2016-01-14 16:04:28 +0100409 if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +0200410 ERR(session, "SSH channel poll error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko428087d2016-01-14 16:04:28 +0100411 session->status = NC_STATUS_INVALID;
412 session->term_reason = NC_SESSION_TERM_OTHER;
413 return -1;
414 } else if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +0200415 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko428087d2016-01-14 16:04:28 +0100416 session->status = NC_STATUS_INVALID;
417 session->term_reason = NC_SESSION_TERM_DROPPED;
418 return -1;
419 } else if (ret > 0) {
420 /* fake it */
421 ret = 1;
422 fds.revents = POLLIN;
Michal Vasko5550cda2016-02-03 15:28:57 +0100423 } else { /* ret == 0 */
424 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100425 }
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100426 break;
roman506354a2024-04-11 09:37:22 +0200427 case NC_TI_TLS:
roman0fa69ee2024-04-23 15:11:02 +0200428 ret = nc_tls_get_num_pending_bytes_wrap(session->ti.tls.session);
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100429 if (ret) {
430 /* some buffered TLS data available */
431 ret = 1;
432 fds.revents = POLLIN;
433 break;
Michal Vasko428087d2016-01-14 16:04:28 +0100434 }
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100435
romana2481092024-04-05 12:30:22 +0200436 fds.fd = nc_tls_get_fd_wrap(session);
roman2eab4742023-06-06 10:00:26 +0200437#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200438 /* fallthrough */
Michal Vasko428087d2016-01-14 16:04:28 +0100439 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200440 case NC_TI_UNIX:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200441 if (session->ti_type == NC_TI_FD) {
Michal Vasko428087d2016-01-14 16:04:28 +0100442 fds.fd = session->ti.fd.in;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200443 } else if (session->ti_type == NC_TI_UNIX) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200444 fds.fd = session->ti.unixsock.sock;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200445 }
Michal Vasko428087d2016-01-14 16:04:28 +0100446
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100447 fds.events = POLLIN;
448 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100449
Michal Vasko63b92d62024-04-29 10:04:56 +0200450 ret = nc_poll(&fds, 1, io_timeout);
Michal Vasko428087d2016-01-14 16:04:28 +0100451 break;
452
453 default:
454 ERRINT;
455 return -1;
456 }
457
458 /* process the poll result, unified ret meaning for poll and ssh_channel poll */
459 if (ret < 0) {
460 /* poll failed - something really bad happened, close the session */
Michal Vasko05532772021-06-03 12:12:38 +0200461 ERR(session, "poll error (%s).", strerror(errno));
Michal Vasko428087d2016-01-14 16:04:28 +0100462 session->status = NC_STATUS_INVALID;
463 session->term_reason = NC_SESSION_TERM_OTHER;
464 return -1;
465 } else { /* status > 0 */
466 /* in case of standard (non-libssh) poll, there still can be an error */
Michal Vasko428087d2016-01-14 16:04:28 +0100467 if (fds.revents & POLLERR) {
Michal Vasko05532772021-06-03 12:12:38 +0200468 ERR(session, "Communication channel error.");
Michal Vasko428087d2016-01-14 16:04:28 +0100469 session->status = NC_STATUS_INVALID;
470 session->term_reason = NC_SESSION_TERM_OTHER;
471 return -1;
472 }
Robin Jarryf732adc2020-05-15 11:18:38 +0200473 /* Some poll() implementations may return POLLHUP|POLLIN when the other
474 * side has closed but there is data left to read in the buffer. */
475 if ((fds.revents & POLLHUP) && !(fds.revents & POLLIN)) {
Michal Vasko05532772021-06-03 12:12:38 +0200476 ERR(session, "Communication channel unexpectedly closed.");
Robin Jarryf732adc2020-05-15 11:18:38 +0200477 session->status = NC_STATUS_INVALID;
478 session->term_reason = NC_SESSION_TERM_DROPPED;
479 return -1;
480 }
Michal Vasko428087d2016-01-14 16:04:28 +0100481 }
482
483 return ret;
484}
485
Michal Vasko77367452021-02-16 16:32:18 +0100486int
487nc_read_msg_poll_io(struct nc_session *session, int io_timeout, struct ly_in **msg)
Radek Krejci206fcd62015-10-07 15:42:48 +0200488{
Michal Vasko428087d2016-01-14 16:04:28 +0100489 int ret;
Radek Krejci206fcd62015-10-07 15:42:48 +0200490
Michal Vasko77367452021-02-16 16:32:18 +0100491 assert(msg);
492 *msg = NULL;
Radek Krejci206fcd62015-10-07 15:42:48 +0200493
Michal Vasko428087d2016-01-14 16:04:28 +0100494 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200495 ERR(session, "Invalid session to read from.");
Michal Vasko77367452021-02-16 16:32:18 +0100496 return -1;
Radek Krejci206fcd62015-10-07 15:42:48 +0200497 }
498
Michal Vasko131120a2018-05-29 15:44:02 +0200499 /* SESSION IO LOCK */
500 ret = nc_session_io_lock(session, io_timeout, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100501 if (ret < 1) {
502 return ret;
Michal Vasko131120a2018-05-29 15:44:02 +0200503 }
504
505 ret = nc_read_poll(session, io_timeout);
Michal Vasko77367452021-02-16 16:32:18 +0100506 if (ret < 1) {
507 /* timed out or error */
Michal Vasko131120a2018-05-29 15:44:02 +0200508
509 /* SESSION IO UNLOCK */
510 nc_session_io_unlock(session, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100511 return ret;
Radek Krejci206fcd62015-10-07 15:42:48 +0200512 }
513
Michal Vasko131120a2018-05-29 15:44:02 +0200514 /* SESSION IO LOCK passed down */
Michal Vasko77367452021-02-16 16:32:18 +0100515 return nc_read_msg_io(session, io_timeout, msg, 1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200516}
Radek Krejcife0b3472015-10-12 13:43:42 +0200517
Michal Vasko428087d2016-01-14 16:04:28 +0100518/* does not really log, only fatal errors */
519int
romane5675b12024-03-05 14:26:23 +0100520nc_session_is_connected(const struct nc_session *session)
Michal Vasko428087d2016-01-14 16:04:28 +0100521{
522 int ret;
523 struct pollfd fds;
524
525 switch (session->ti_type) {
526 case NC_TI_FD:
527 fds.fd = session->ti.fd.in;
528 break;
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200529 case NC_TI_UNIX:
530 fds.fd = session->ti.unixsock.sock;
531 break;
roman2eab4742023-06-06 10:00:26 +0200532#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200533 case NC_TI_SSH:
Michal Vasko840a8a62017-02-07 10:56:34 +0100534 return ssh_is_connected(session->ti.libssh.session);
roman506354a2024-04-11 09:37:22 +0200535 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200536 fds.fd = nc_tls_get_fd_wrap(session);
Michal Vasko428087d2016-01-14 16:04:28 +0100537 break;
roman2eab4742023-06-06 10:00:26 +0200538#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof945da52018-02-15 08:45:13 +0100539 default:
Michal Vasko428087d2016-01-14 16:04:28 +0100540 return 0;
541 }
542
Michal Vasko840a8a62017-02-07 10:56:34 +0100543 if (fds.fd == -1) {
544 return 0;
545 }
546
Michal Vasko428087d2016-01-14 16:04:28 +0100547 fds.events = POLLIN;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100548 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100549
Michal Vasko63b92d62024-04-29 10:04:56 +0200550 ret = nc_poll(&fds, 1, 0);
Michal Vasko428087d2016-01-14 16:04:28 +0100551 if (ret == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100552 return 0;
553 } else if ((ret > 0) && (fds.revents & (POLLHUP | POLLERR))) {
554 return 0;
555 }
556
557 return 1;
558}
559
Radek Krejcife0b3472015-10-12 13:43:42 +0200560#define WRITE_BUFSIZE (2 * BUFFERSIZE)
Michal Vasko3a8654b2024-05-13 09:43:49 +0200561struct nc_wclb_arg {
Radek Krejcife0b3472015-10-12 13:43:42 +0200562 struct nc_session *session;
563 char buf[WRITE_BUFSIZE];
Michal Vasko3a8654b2024-05-13 09:43:49 +0200564 uint32_t len;
Radek Krejcife0b3472015-10-12 13:43:42 +0200565};
566
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200567/**
568 * @brief Write to a NETCONF session.
569 *
570 * @param[in] session Session to write to.
571 * @param[in] buf Buffer to write.
572 * @param[in] count Count of bytes from @p buf to write.
573 * @return Number of bytes written.
574 * @return -1 on error.
575 */
Michal Vasko964e1732016-09-23 13:39:33 +0200576static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200577nc_write(struct nc_session *session, const void *buf, uint32_t count)
Radek Krejcife0b3472015-10-12 13:43:42 +0200578{
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200579 int c, fd, interrupted;
Michal Vasko3a8654b2024-05-13 09:43:49 +0200580 uint32_t written = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200581
Michal Vasko428087d2016-01-14 16:04:28 +0100582 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
583 return -1;
584 }
585
586 /* prevent SIGPIPE this way */
587 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +0200588 ERR(session, "Communication socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +0100589 session->status = NC_STATUS_INVALID;
590 session->term_reason = NC_SESSION_TERM_DROPPED;
Michal Vasko428087d2016-01-14 16:04:28 +0100591 return -1;
592 }
593
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200594 DBG(session, "Sending message:\n%.*s\n", (int)count, buf);
Michal Vasko160b7912016-06-20 10:00:53 +0200595
Michal Vasko81b33fb2016-09-26 14:57:36 +0200596 do {
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200597 interrupted = 0;
Michal Vasko964e1732016-09-23 13:39:33 +0200598 switch (session->ti_type) {
Michal Vasko964e1732016-09-23 13:39:33 +0200599 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200600 case NC_TI_UNIX:
601 fd = session->ti_type == NC_TI_FD ? session->ti.fd.out : session->ti.unixsock.sock;
602 c = write(fd, (char *)(buf + written), count - written);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200603 if ((c < 0) && (errno == EAGAIN)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200604 c = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200605 } else if ((c < 0) && (errno == EINTR)) {
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200606 c = 0;
607 interrupted = 1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200608 } else if (c < 0) {
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200609 ERR(session, "Socket error (%s).", strerror(errno));
Michal Vasko964e1732016-09-23 13:39:33 +0200610 return -1;
611 }
612 break;
Radek Krejcife0b3472015-10-12 13:43:42 +0200613
roman2eab4742023-06-06 10:00:26 +0200614#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200615 case NC_TI_SSH:
Michal Vasko964e1732016-09-23 13:39:33 +0200616 if (ssh_channel_is_closed(session->ti.libssh.channel) || ssh_channel_is_eof(session->ti.libssh.channel)) {
617 if (ssh_channel_is_closed(session->ti.libssh.channel)) {
Michal Vasko05532772021-06-03 12:12:38 +0200618 ERR(session, "SSH channel unexpectedly closed.");
Michal Vasko964e1732016-09-23 13:39:33 +0200619 } else {
Michal Vasko05532772021-06-03 12:12:38 +0200620 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko964e1732016-09-23 13:39:33 +0200621 }
622 session->status = NC_STATUS_INVALID;
623 session->term_reason = NC_SESSION_TERM_DROPPED;
624 return -1;
Michal Vasko454e22b2016-01-21 15:34:08 +0100625 }
Michal Vasko81b33fb2016-09-26 14:57:36 +0200626 c = ssh_channel_write(session->ti.libssh.channel, (char *)(buf + written), count - written);
Michal Vasko964e1732016-09-23 13:39:33 +0200627 if ((c == SSH_ERROR) || (c == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200628 ERR(session, "SSH channel write failed.");
Michal Vasko964e1732016-09-23 13:39:33 +0200629 return -1;
630 }
631 break;
roman506354a2024-04-11 09:37:22 +0200632 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200633 c = nc_tls_write_wrap(session, (const unsigned char *)(buf + written), count - written);
634 if (c < 0) {
635 /* possible client dc, or some socket/TLS communication error */
636 return -1;
Michal Vasko964e1732016-09-23 13:39:33 +0200637 }
638 break;
roman2eab4742023-06-06 10:00:26 +0200639#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko339eea82016-09-29 11:42:36 +0200640 default:
641 ERRINT;
642 return -1;
Michal Vasko964e1732016-09-23 13:39:33 +0200643 }
644
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200645 if ((c == 0) && !interrupted) {
Michal Vasko964e1732016-09-23 13:39:33 +0200646 /* we must wait */
647 usleep(NC_TIMEOUT_STEP);
648 }
649
650 written += c;
Michal Vasko81b33fb2016-09-26 14:57:36 +0200651 } while (written < count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200652
Michal Vasko964e1732016-09-23 13:39:33 +0200653 return written;
Radek Krejcife0b3472015-10-12 13:43:42 +0200654}
655
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200656/**
657 * @brief Write the start tag and the message part of a chunked-framing NETCONF message.
658 *
659 * @param[in] session Session to write to.
660 * @param[in] buf Message buffer to write.
661 * @param[in] count Count of bytes from @p buf to write.
662 * @return Number of bytes written.
663 * @return -1 on error.
664 */
Michal Vasko428087d2016-01-14 16:04:28 +0100665static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200666nc_write_starttag_and_msg(struct nc_session *session, const void *buf, uint32_t count)
Michal Vasko086311b2016-01-08 09:53:11 +0100667{
Michal Vasko3a8654b2024-05-13 09:43:49 +0200668 int ret = 0, r;
Claus Klein22091912020-01-20 13:45:47 +0100669 char chunksize[24];
Michal Vasko086311b2016-01-08 09:53:11 +0100670
671 if (session->version == NC_VERSION_11) {
Michal Vasko3a8654b2024-05-13 09:43:49 +0200672 r = sprintf(chunksize, "\n#%" PRIu32 "\n", count);
673
674 r = nc_write(session, chunksize, r);
675 if (r == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100676 return -1;
677 }
Michal Vasko3a8654b2024-05-13 09:43:49 +0200678 ret += r;
Michal Vasko086311b2016-01-08 09:53:11 +0100679 }
Michal Vasko428087d2016-01-14 16:04:28 +0100680
Michal Vasko3a8654b2024-05-13 09:43:49 +0200681 r = nc_write(session, buf, count);
682 if (r == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100683 return -1;
684 }
Michal Vasko3a8654b2024-05-13 09:43:49 +0200685 ret += r;
Michal Vasko428087d2016-01-14 16:04:28 +0100686
687 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100688}
689
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200690/**
691 * @brief Write the end tag part of a chunked-framing NETCONF message.
692 *
693 * @param[in] session Session to write to.
694 * @return Number of bytes written.
695 * @return -1 on error.
696 */
Radek Krejcife0b3472015-10-12 13:43:42 +0200697static int
Michal Vasko428087d2016-01-14 16:04:28 +0100698nc_write_endtag(struct nc_session *session)
Radek Krejcife0b3472015-10-12 13:43:42 +0200699{
Michal Vasko428087d2016-01-14 16:04:28 +0100700 int ret;
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100701
Michal Vasko428087d2016-01-14 16:04:28 +0100702 if (session->version == NC_VERSION_11) {
703 ret = nc_write(session, "\n##\n", 4);
704 } else {
705 ret = nc_write(session, "]]>]]>", 6);
Radek Krejcife0b3472015-10-12 13:43:42 +0200706 }
707
Michal Vasko428087d2016-01-14 16:04:28 +0100708 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200709}
710
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200711/**
712 * @brief Flush all the data buffered for writing.
713 *
714 * @param[in] warg Write callback structure to flush.
715 * @return Number of written bytes.
716 * @return -1 on error.
717 */
Michal Vasko428087d2016-01-14 16:04:28 +0100718static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200719nc_write_clb_flush(struct nc_wclb_arg *warg)
Radek Krejcife0b3472015-10-12 13:43:42 +0200720{
Michal Vasko428087d2016-01-14 16:04:28 +0100721 int ret = 0;
722
Radek Krejcife0b3472015-10-12 13:43:42 +0200723 /* flush current buffer */
724 if (warg->len) {
Michal Vasko428087d2016-01-14 16:04:28 +0100725 ret = nc_write_starttag_and_msg(warg->session, warg->buf, warg->len);
Radek Krejcife0b3472015-10-12 13:43:42 +0200726 warg->len = 0;
727 }
Michal Vasko428087d2016-01-14 16:04:28 +0100728
729 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200730}
731
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200732/**
733 * @brief Write callback buffering the data in a write structure.
734 *
735 * @param[in] arg Write structure used for buffering.
736 * @param[in] buf Buffer to write.
737 * @param[in] count Count of bytes to write from @p buf.
738 * @param[in] xmlcontent Whether the data are actually printed as part of an XML in which case they need to be encoded.
739 * @return Number of written bytes.
740 * @return -1 on error.
741 */
Radek Krejcife0b3472015-10-12 13:43:42 +0200742static ssize_t
Michal Vasko3a8654b2024-05-13 09:43:49 +0200743nc_write_clb(void *arg, const void *buf, uint32_t count, int xmlcontent)
Radek Krejcife0b3472015-10-12 13:43:42 +0200744{
Michal Vasko3a8654b2024-05-13 09:43:49 +0200745 ssize_t ret = 0, c;
746 uint32_t l;
747 struct nc_wclb_arg *warg = arg;
Radek Krejcife0b3472015-10-12 13:43:42 +0200748
749 if (!buf) {
Michal Vasko428087d2016-01-14 16:04:28 +0100750 c = nc_write_clb_flush(warg);
751 if (c == -1) {
752 return -1;
753 }
754 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200755
756 /* endtag */
Michal Vasko428087d2016-01-14 16:04:28 +0100757 c = nc_write_endtag(warg->session);
758 if (c == -1) {
759 return -1;
760 }
761 ret += c;
762
763 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200764 }
765
766 if (warg->len && (warg->len + count > WRITE_BUFSIZE)) {
767 /* dump current buffer */
Michal Vasko428087d2016-01-14 16:04:28 +0100768 c = nc_write_clb_flush(warg);
769 if (c == -1) {
770 return -1;
771 }
772 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200773 }
Michal Vasko428087d2016-01-14 16:04:28 +0100774
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200775 if (!xmlcontent && (count > WRITE_BUFSIZE)) {
Radek Krejcife0b3472015-10-12 13:43:42 +0200776 /* write directly */
Michal Vasko428087d2016-01-14 16:04:28 +0100777 c = nc_write_starttag_and_msg(warg->session, buf, count);
778 if (c == -1) {
779 return -1;
780 }
781 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200782 } else {
783 /* keep in buffer and write later */
Radek Krejci047300e2016-03-08 16:46:58 +0100784 if (xmlcontent) {
785 for (l = 0; l < count; l++) {
786 if (warg->len + 5 >= WRITE_BUFSIZE) {
787 /* buffer is full */
788 c = nc_write_clb_flush(warg);
789 if (c == -1) {
790 return -1;
791 }
792 }
793
794 switch (((char *)buf)[l]) {
795 case '&':
796 ret += 5;
797 memcpy(&warg->buf[warg->len], "&amp;", 5);
798 warg->len += 5;
799 break;
800 case '<':
801 ret += 4;
802 memcpy(&warg->buf[warg->len], "&lt;", 4);
803 warg->len += 4;
804 break;
805 case '>':
806 /* not needed, just for readability */
807 ret += 4;
808 memcpy(&warg->buf[warg->len], "&gt;", 4);
809 warg->len += 4;
810 break;
811 default:
812 ret++;
813 memcpy(&warg->buf[warg->len], &((char *)buf)[l], 1);
814 warg->len++;
815 }
816 }
817 } else {
818 memcpy(&warg->buf[warg->len], buf, count);
819 warg->len += count; /* is <= WRITE_BUFSIZE */
820 ret += count;
821 }
Radek Krejcife0b3472015-10-12 13:43:42 +0200822 }
823
Michal Vasko428087d2016-01-14 16:04:28 +0100824 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200825}
826
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200827/**
828 * @brief Write print callback used by libyang.
829 */
Radek Krejci047300e2016-03-08 16:46:58 +0100830static ssize_t
831nc_write_xmlclb(void *arg, const void *buf, size_t count)
832{
Michal Vasko77367452021-02-16 16:32:18 +0100833 ssize_t r;
Radek Krejci047300e2016-03-08 16:46:58 +0100834
Michal Vasko77367452021-02-16 16:32:18 +0100835 r = nc_write_clb(arg, buf, count, 0);
836 if (r == -1) {
837 return -1;
Michal Vasko08611b32016-12-05 13:30:37 +0100838 }
839
Michal Vasko77367452021-02-16 16:32:18 +0100840 /* always return what libyang expects, simply that all the characters were printed */
841 return count;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100842}
843
Michal Vasko131120a2018-05-29 15:44:02 +0200844/* return NC_MSG_ERROR can change session status, acquires IO lock as needed */
845NC_MSG_TYPE
846nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
Radek Krejcife0b3472015-10-12 13:43:42 +0200847{
Radek Krejcid116db42016-01-08 15:36:30 +0100848 va_list ap;
Michal Vasko131120a2018-05-29 15:44:02 +0200849 int count, ret;
Michal Vasko70bf2222021-10-26 10:45:15 +0200850 const char *attrs, *str;
Michal Vaskoff7286b2021-07-09 13:13:11 +0200851 struct lyd_node *op, *reply_envp, *node, *next;
Michal Vasko77367452021-02-16 16:32:18 +0100852 struct lyd_node_opaq *rpc_envp;
Radek Krejci93e80222016-10-03 13:34:25 +0200853 struct nc_server_notif *notif;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100854 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +0100855 char *buf;
Michal Vasko3a8654b2024-05-13 09:43:49 +0200856 struct nc_wclb_arg arg;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200857 const char **capabilities;
Michal Vasko77367452021-02-16 16:32:18 +0100858 uint32_t *sid = NULL, i, wd = 0;
859 LY_ERR lyrc;
Radek Krejcife0b3472015-10-12 13:43:42 +0200860
Michal Vasko428087d2016-01-14 16:04:28 +0100861 assert(session);
862
863 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200864 ERR(session, "Invalid session to write to.");
Michal Vasko131120a2018-05-29 15:44:02 +0200865 return NC_MSG_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +0100866 }
867
Radek Krejcife0b3472015-10-12 13:43:42 +0200868 arg.session = session;
869 arg.len = 0;
870
Michal Vasko131120a2018-05-29 15:44:02 +0200871 /* SESSION IO LOCK */
872 ret = nc_session_io_lock(session, io_timeout, __func__);
873 if (ret < 0) {
874 return NC_MSG_ERROR;
875 } else if (!ret) {
876 return NC_MSG_WOULDBLOCK;
877 }
878
879 va_start(ap, type);
Radek Krejci127f8952016-10-12 14:57:16 +0200880
Radek Krejcife0b3472015-10-12 13:43:42 +0200881 switch (type) {
882 case NC_MSG_RPC:
Michal Vasko77367452021-02-16 16:32:18 +0100883 op = va_arg(ap, struct lyd_node *);
Radek Krejcife0b3472015-10-12 13:43:42 +0200884 attrs = va_arg(ap, const char *);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100885
Michal Vasko70bf2222021-10-26 10:45:15 +0200886 /* <rpc> open */
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200887 count = asprintf(&buf, "<rpc xmlns=\"%s\" message-id=\"%" PRIu64 "\"%s>",
Michal Vasko77367452021-02-16 16:32:18 +0100888 NC_NS_BASE, session->opts.client.msgid + 1, attrs ? attrs : "");
roman3a95bb22023-10-26 11:07:17 +0200889 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +0100890 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +0200891 free(buf);
Michal Vaskoe1708602016-10-18 12:17:22 +0200892
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100893 if (op->schema && (op->schema->nodetype & (LYS_CONTAINER | LYS_LIST))) {
Michal Vasko70bf2222021-10-26 10:45:15 +0200894 /* <action> open */
895 str = "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">";
896 nc_write_clb((void *)&arg, str, strlen(str), 0);
897 }
898
899 /* rpc data */
Michal Vasko6b70b822022-01-21 08:39:16 +0100900 if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, op, LYD_XML, LYD_PRINT_SHRINK | LYD_PRINT_KEEPEMPTYCONT)) {
Michal Vasko131120a2018-05-29 15:44:02 +0200901 ret = NC_MSG_ERROR;
902 goto cleanup;
Michal Vasko5a91ce72017-10-19 11:30:02 +0200903 }
Michal Vasko70bf2222021-10-26 10:45:15 +0200904
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100905 if (op->schema && (op->schema->nodetype & (LYS_CONTAINER | LYS_LIST))) {
Michal Vasko70bf2222021-10-26 10:45:15 +0200906 /* <action> close */
907 str = "</action>";
908 nc_write_clb((void *)&arg, str, strlen(str), 0);
909 }
910
911 /* <rpc> close */
912 str = "</rpc>";
913 nc_write_clb((void *)&arg, str, strlen(str), 0);
Radek Krejcife0b3472015-10-12 13:43:42 +0200914
Michal Vasko2e6defd2016-10-07 15:48:15 +0200915 session->opts.client.msgid++;
Radek Krejcife0b3472015-10-12 13:43:42 +0200916 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100917
Radek Krejcife0b3472015-10-12 13:43:42 +0200918 case NC_MSG_REPLY:
Michal Vasko77367452021-02-16 16:32:18 +0100919 rpc_envp = va_arg(ap, struct lyd_node_opaq *);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100920 reply = va_arg(ap, struct nc_server_reply *);
921
Michal Vasko77367452021-02-16 16:32:18 +0100922 if (!rpc_envp) {
923 /* can be NULL if replying with a malformed-message error */
924 nc_write_clb((void *)&arg, "<rpc-reply xmlns=\"" NC_NS_BASE "\">", 18 + strlen(NC_NS_BASE) + 2, 0);
925
926 assert(reply->type == NC_RPL_ERROR);
927 if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, ((struct nc_server_reply_error *)reply)->err, LYD_XML,
928 LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)) {
929 ret = NC_MSG_ERROR;
930 goto cleanup;
931 }
932
933 nc_write_clb((void *)&arg, "</rpc-reply>", 12, 0);
934 break;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +0100935 }
936
Michal Vasko77367452021-02-16 16:32:18 +0100937 /* build a rpc-reply opaque node that can be simply printed */
Michal Vaskoff7286b2021-07-09 13:13:11 +0200938 if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns,
939 &reply_envp)) {
Michal Vasko77367452021-02-16 16:32:18 +0100940 ERRINT;
941 ret = NC_MSG_ERROR;
942 goto cleanup;
Michal Vaskoe7e534f2016-01-15 09:51:09 +0100943 }
Michal Vasko77367452021-02-16 16:32:18 +0100944
Michal Vasko05ba9df2016-01-13 14:40:27 +0100945 switch (reply->type) {
946 case NC_RPL_OK:
Michal Vasko77367452021-02-16 16:32:18 +0100947 if (lyd_new_opaq2(reply_envp, NULL, "ok", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns, NULL)) {
948 lyd_free_tree(reply_envp);
949
950 ERRINT;
951 ret = NC_MSG_ERROR;
952 goto cleanup;
Michal Vasko08611b32016-12-05 13:30:37 +0100953 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100954 break;
955 case NC_RPL_DATA:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200956 switch (((struct nc_server_reply_data *)reply)->wd) {
Radek Krejci36dfdb32016-09-01 16:56:35 +0200957 case NC_WD_UNKNOWN:
958 case NC_WD_EXPLICIT:
Michal Vasko77367452021-02-16 16:32:18 +0100959 wd = LYD_PRINT_WD_EXPLICIT;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200960 break;
961 case NC_WD_TRIM:
Michal Vasko77367452021-02-16 16:32:18 +0100962 wd = LYD_PRINT_WD_TRIM;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200963 break;
964 case NC_WD_ALL:
Michal Vasko77367452021-02-16 16:32:18 +0100965 wd = LYD_PRINT_WD_ALL;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200966 break;
967 case NC_WD_ALL_TAG:
Michal Vasko77367452021-02-16 16:32:18 +0100968 wd = LYD_PRINT_WD_ALL_TAG;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200969 break;
970 }
Michal Vasko77367452021-02-16 16:32:18 +0100971
972 node = ((struct nc_server_reply_data *)reply)->data;
973 assert(node->schema->nodetype & (LYS_RPC | LYS_ACTION));
Michal Vaskoff7286b2021-07-09 13:13:11 +0200974 LY_LIST_FOR_SAFE(lyd_child(node), next, node) {
Michal Vasko77367452021-02-16 16:32:18 +0100975 /* temporary */
Michal Vaskoff7286b2021-07-09 13:13:11 +0200976 lyd_insert_child(reply_envp, node);
Michal Vasko5a91ce72017-10-19 11:30:02 +0200977 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100978 break;
979 case NC_RPL_ERROR:
Michal Vasko77367452021-02-16 16:32:18 +0100980 /* temporary */
981 lyd_insert_child(reply_envp, ((struct nc_server_reply_error *)reply)->err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100982 break;
983 default:
984 ERRINT;
Radek Krejci047300e2016-03-08 16:46:58 +0100985 nc_write_clb((void *)&arg, NULL, 0, 0);
Michal Vasko131120a2018-05-29 15:44:02 +0200986 ret = NC_MSG_ERROR;
987 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100988 }
Michal Vasko77367452021-02-16 16:32:18 +0100989
990 /* temporary */
991 ((struct lyd_node_opaq *)reply_envp)->attr = rpc_envp->attr;
992
993 /* print */
994 lyrc = lyd_print_clb(nc_write_xmlclb, (void *)&arg, reply_envp, LYD_XML, LYD_PRINT_SHRINK | wd);
995 ((struct lyd_node_opaq *)reply_envp)->attr = NULL;
996
997 /* cleanup */
998 switch (reply->type) {
999 case NC_RPL_OK:
1000 /* just free everything */
1001 lyd_free_tree(reply_envp);
1002 break;
1003 case NC_RPL_DATA:
Michal Vaskoff7286b2021-07-09 13:13:11 +02001004 LY_LIST_FOR_SAFE(lyd_child(reply_envp), next, node) {
Michal Vasko77367452021-02-16 16:32:18 +01001005 /* connect back to the reply structure */
Michal Vaskoff7286b2021-07-09 13:13:11 +02001006 lyd_insert_child(((struct nc_server_reply_data *)reply)->data, node);
Michal Vasko77367452021-02-16 16:32:18 +01001007 }
1008 lyd_free_tree(reply_envp);
1009 break;
1010 case NC_RPL_ERROR:
1011 /* unlink from the data reply */
1012 lyd_unlink_tree(lyd_child(reply_envp));
1013 lyd_free_tree(reply_envp);
1014 break;
1015 default:
1016 break;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +01001017 }
Michal Vasko77367452021-02-16 16:32:18 +01001018
1019 if (lyrc) {
1020 ret = NC_MSG_ERROR;
1021 goto cleanup;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +01001022 }
Radek Krejcife0b3472015-10-12 13:43:42 +02001023 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +01001024
Radek Krejcife0b3472015-10-12 13:43:42 +02001025 case NC_MSG_NOTIF:
Radek Krejci93e80222016-10-03 13:34:25 +02001026 notif = va_arg(ap, struct nc_server_notif *);
1027
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001028 nc_write_clb((void *)&arg, "<notification xmlns=\""NC_NS_NOTIF "\">", 21 + 47 + 2, 0);
Radek Krejci93e80222016-10-03 13:34:25 +02001029 nc_write_clb((void *)&arg, "<eventTime>", 11, 0);
1030 nc_write_clb((void *)&arg, notif->eventtime, strlen(notif->eventtime), 0);
1031 nc_write_clb((void *)&arg, "</eventTime>", 12, 0);
Michal Vasko77367452021-02-16 16:32:18 +01001032 if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, notif->ntf, LYD_XML, LYD_PRINT_SHRINK)) {
Michal Vasko131120a2018-05-29 15:44:02 +02001033 ret = NC_MSG_ERROR;
1034 goto cleanup;
Michal Vasko5a91ce72017-10-19 11:30:02 +02001035 }
mohitarora24878b2962016-11-09 18:45:33 -05001036 nc_write_clb((void *)&arg, "</notification>", 15, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +02001037 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +01001038
Radek Krejcid116db42016-01-08 15:36:30 +01001039 case NC_MSG_HELLO:
1040 if (session->version != NC_VERSION_10) {
Michal Vasko131120a2018-05-29 15:44:02 +02001041 ret = NC_MSG_ERROR;
1042 goto cleanup;
Radek Krejcid116db42016-01-08 15:36:30 +01001043 }
1044 capabilities = va_arg(ap, const char **);
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001045 sid = va_arg(ap, uint32_t *);
Michal Vasko05ba9df2016-01-13 14:40:27 +01001046
Radek Krejcid116db42016-01-08 15:36:30 +01001047 count = asprintf(&buf, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
roman3a95bb22023-10-26 11:07:17 +02001048 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +01001049 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001050 free(buf);
1051 for (i = 0; capabilities[i]; i++) {
Radek Krejci047300e2016-03-08 16:46:58 +01001052 nc_write_clb((void *)&arg, "<capability>", 12, 0);
1053 nc_write_clb((void *)&arg, capabilities[i], strlen(capabilities[i]), 1);
1054 nc_write_clb((void *)&arg, "</capability>", 13, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001055 }
1056 if (sid) {
Michal Vasko16374712024-04-26 14:13:00 +02001057 count = asprintf(&buf, "</capabilities><session-id>%" PRIu32 "</session-id></hello>", *sid);
roman3a95bb22023-10-26 11:07:17 +02001058 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +01001059 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001060 free(buf);
1061 } else {
Radek Krejci047300e2016-03-08 16:46:58 +01001062 nc_write_clb((void *)&arg, "</capabilities></hello>", 23, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001063 }
Radek Krejcid116db42016-01-08 15:36:30 +01001064 break;
Michal Vaskoed462342016-01-12 12:33:48 +01001065
Radek Krejcife0b3472015-10-12 13:43:42 +02001066 default:
Michal Vasko131120a2018-05-29 15:44:02 +02001067 ret = NC_MSG_ERROR;
1068 goto cleanup;
Radek Krejcife0b3472015-10-12 13:43:42 +02001069 }
1070
1071 /* flush message */
Radek Krejci047300e2016-03-08 16:46:58 +01001072 nc_write_clb((void *)&arg, NULL, 0, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +02001073
Michal Vasko428087d2016-01-14 16:04:28 +01001074 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
1075 /* error was already written */
Michal Vasko131120a2018-05-29 15:44:02 +02001076 ret = NC_MSG_ERROR;
1077 } else {
1078 /* specific message successfully sent */
1079 ret = type;
Michal Vasko428087d2016-01-14 16:04:28 +01001080 }
1081
Michal Vasko131120a2018-05-29 15:44:02 +02001082cleanup:
1083 va_end(ap);
1084 nc_session_io_unlock(session, __func__);
1085 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +02001086}
Michal Vasko4eb3c312016-03-01 14:09:37 +01001087
1088void *
1089nc_realloc(void *ptr, size_t size)
1090{
1091 void *ret;
1092
1093 ret = realloc(ptr, size);
1094 if (!ret) {
1095 free(ptr);
1096 }
1097
1098 return ret;
1099}
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001100
1101struct passwd *
romanf6e32012023-04-24 15:51:26 +02001102nc_getpw(uid_t uid, const char *username, struct passwd *pwd_buf, char **buf, size_t *buf_size)
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001103{
1104 struct passwd *pwd = NULL;
Michal Vasko7e06ee52021-11-02 08:53:05 +01001105 long sys_size;
1106 int ret;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001107
1108 do {
Michal Vasko7e06ee52021-11-02 08:53:05 +01001109 if (!*buf_size) {
1110 /* learn suitable buffer size */
1111 sys_size = sysconf(_SC_GETPW_R_SIZE_MAX);
1112 *buf_size = (sys_size == -1) ? 2048 : sys_size;
1113 } else {
1114 /* enlarge buffer */
1115 *buf_size += 2048;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001116 }
1117
Michal Vasko7e06ee52021-11-02 08:53:05 +01001118 /* allocate some buffer */
1119 *buf = nc_realloc(*buf, *buf_size);
roman3a95bb22023-10-26 11:07:17 +02001120 NC_CHECK_ERRMEM_RET(!*buf, NULL);
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001121
romanf6e32012023-04-24 15:51:26 +02001122 if (username) {
1123 ret = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd);
1124 } else {
1125 ret = getpwuid_r(uid, pwd_buf, *buf, *buf_size, &pwd);
1126 }
Michal Vasko7e06ee52021-11-02 08:53:05 +01001127 } while (ret && (ret == ERANGE));
1128
1129 if (ret) {
romanf6e32012023-04-24 15:51:26 +02001130 if (username) {
1131 ERR(NULL, "Retrieving username \"%s\" passwd entry failed (%s).", username, strerror(ret));
1132 } else {
1133 ERR(NULL, "Retrieving UID \"%lu\" passwd entry failed (%s).", (unsigned long)uid, strerror(ret));
1134 }
Michal Vasko7e06ee52021-11-02 08:53:05 +01001135 }
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001136 return pwd;
1137}