blob: daae02328e6a6675dd54dd981b2703eaa461ba74 [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>
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +020022#include <pwd.h>
Radek Krejcife0b3472015-10-12 13:43:42 +020023#include <stdarg.h>
roman3f9b65c2023-06-05 14:26:58 +020024#include <stdint.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020025#include <stdlib.h>
26#include <string.h>
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +020027#include <sys/types.h>
Michal Vasko36c7be82017-02-22 13:37:59 +010028#include <time.h>
Michal Vaskob83a3fa2021-05-26 09:53:42 +020029#include <unistd.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020030
31#include <libyang/libyang.h>
32
romana2481092024-04-05 12:30:22 +020033#include "compat.h"
roman008cfe72024-04-05 12:36:18 +020034#include "config.h"
roman3f9b65c2023-06-05 14:26:58 +020035#include "log_p.h"
36#include "messages_p.h"
37#include "netconf.h"
38#include "session.h"
39#include "session_p.h"
roman0fa69ee2024-04-23 15:11:02 +020040#include "session_wrapper.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020041
Michal Vasko8fe604c2020-02-10 15:25:04 +010042const char *nc_msgtype2str[] = {
43 "error",
44 "would block",
45 "no message",
46 "hello message",
47 "bad hello message",
48 "RPC message",
49 "rpc-reply message",
50 "rpc-reply message with wrong ID",
51 "notification message",
52};
53
Radek Krejcife0b3472015-10-12 13:43:42 +020054#define BUFFERSIZE 512
Radek Krejci206fcd62015-10-07 15:42:48 +020055
56static ssize_t
Michal Vasko3a8654b2024-05-13 09:43:49 +020057nc_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 +020058{
Michal Vasko3a8654b2024-05-13 09:43:49 +020059 uint32_t readd = 0;
Michal Vasko9d8bee62016-03-03 10:58:24 +010060 ssize_t r = -1;
Robin Jarry7de4b8e2019-10-14 21:46:00 +020061 int fd, interrupted;
roman6ece9c52022-06-22 09:29:17 +020062 struct timespec ts_inact_timeout;
Radek Krejci206fcd62015-10-07 15:42:48 +020063
64 assert(session);
65 assert(buf);
66
Michal Vasko428087d2016-01-14 16:04:28 +010067 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
68 return -1;
69 }
70
Radek Krejci206fcd62015-10-07 15:42:48 +020071 if (!count) {
72 return 0;
73 }
74
Michal Vaskod8a74192023-02-06 15:51:50 +010075 nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
Michal Vasko81b33fb2016-09-26 14:57:36 +020076 do {
Robin Jarry7de4b8e2019-10-14 21:46:00 +020077 interrupted = 0;
Michal Vasko6b7c42e2016-03-02 15:46:41 +010078 switch (session->ti_type) {
79 case NC_TI_NONE:
80 return 0;
Michal Vasko38a7c6c2015-12-04 12:29:20 +010081
Michal Vasko6b7c42e2016-03-02 15:46:41 +010082 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +020083 case NC_TI_UNIX:
84 fd = (session->ti_type == NC_TI_FD) ? session->ti.fd.in : session->ti.unixsock.sock;
Michal Vasko6b7c42e2016-03-02 15:46:41 +010085 /* read via standard file descriptor */
Olivier Matzac7fa2f2018-10-11 10:02:04 +020086 r = read(fd, buf + readd, count - readd);
Radek Krejci206fcd62015-10-07 15:42:48 +020087 if (r < 0) {
Robin Jarry7de4b8e2019-10-14 21:46:00 +020088 if (errno == EAGAIN) {
Michal Vasko6b7c42e2016-03-02 15:46:41 +010089 r = 0;
90 break;
Robin Jarry7de4b8e2019-10-14 21:46:00 +020091 } else if (errno == EINTR) {
92 r = 0;
93 interrupted = 1;
94 break;
Radek Krejci206fcd62015-10-07 15:42:48 +020095 } else {
Michal Vasko05532772021-06-03 12:12:38 +020096 ERR(session, "Reading from file descriptor (%d) failed (%s).", fd, strerror(errno));
Michal Vasko428087d2016-01-14 16:04:28 +010097 session->status = NC_STATUS_INVALID;
98 session->term_reason = NC_SESSION_TERM_OTHER;
Radek Krejci206fcd62015-10-07 15:42:48 +020099 return -1;
100 }
101 } else if (r == 0) {
Michal Vasko05532772021-06-03 12:12:38 +0200102 ERR(session, "Communication file descriptor (%d) unexpectedly closed.", fd);
Michal Vasko428087d2016-01-14 16:04:28 +0100103 session->status = NC_STATUS_INVALID;
104 session->term_reason = NC_SESSION_TERM_DROPPED;
Radek Krejci206fcd62015-10-07 15:42:48 +0200105 return -1;
106 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100107 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200108
roman2eab4742023-06-06 10:00:26 +0200109#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200110 case NC_TI_SSH:
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100111 /* read via libssh */
Michal Vasko81b33fb2016-09-26 14:57:36 +0200112 r = ssh_channel_read(session->ti.libssh.channel, buf + readd, count - readd, 0);
Radek Krejci206fcd62015-10-07 15:42:48 +0200113 if (r == SSH_AGAIN) {
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100114 r = 0;
115 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200116 } else if (r == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +0200117 ERR(session, "Reading from the SSH channel failed (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko428087d2016-01-14 16:04:28 +0100118 session->status = NC_STATUS_INVALID;
119 session->term_reason = NC_SESSION_TERM_OTHER;
Radek Krejci206fcd62015-10-07 15:42:48 +0200120 return -1;
121 } else if (r == 0) {
122 if (ssh_channel_is_eof(session->ti.libssh.channel)) {
Michal Vasko05532772021-06-03 12:12:38 +0200123 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko428087d2016-01-14 16:04:28 +0100124 session->status = NC_STATUS_INVALID;
125 session->term_reason = NC_SESSION_TERM_DROPPED;
Radek Krejci206fcd62015-10-07 15:42:48 +0200126 return -1;
127 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100128 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200129 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100130 break;
Radek Krejci206fcd62015-10-07 15:42:48 +0200131
roman506354a2024-04-11 09:37:22 +0200132 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200133 r = nc_tls_read_wrap(session, (unsigned char *)buf + readd, count - readd);
134 if (r < 0) {
135 /* non-recoverable error */
136 return r;
Radek Krejci206fcd62015-10-07 15:42:48 +0200137 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100138 break;
roman2eab4742023-06-06 10:00:26 +0200139#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100140 }
141
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100142 if (r == 0) {
Michal Vaskof471fa02017-02-15 10:48:12 +0100143 /* nothing read */
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200144 if (!interrupted) {
145 usleep(NC_TIMEOUT_STEP);
146 }
Michal Vaskod8a74192023-02-06 15:51:50 +0100147 if ((nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) || (nc_timeouttime_cur_diff(ts_act_timeout) < 1)) {
148 if (nc_timeouttime_cur_diff(&ts_inact_timeout) < 1) {
Michal Vasko05532772021-06-03 12:12:38 +0200149 ERR(session, "Inactive read timeout elapsed.");
Michal Vaskof471fa02017-02-15 10:48:12 +0100150 } else {
Michal Vasko05532772021-06-03 12:12:38 +0200151 ERR(session, "Active read timeout elapsed.");
Michal Vaskof471fa02017-02-15 10:48:12 +0100152 }
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100153 session->status = NC_STATUS_INVALID;
154 session->term_reason = NC_SESSION_TERM_OTHER;
155 return -1;
156 }
Michal Vaskof471fa02017-02-15 10:48:12 +0100157 } else {
158 /* something read */
159 readd += r;
Michal Vasko36c7be82017-02-22 13:37:59 +0100160
161 /* reset inactive timeout */
Michal Vaskod8a74192023-02-06 15:51:50 +0100162 nc_timeouttime_get(&ts_inact_timeout, inact_timeout);
Michal Vasko6b7c42e2016-03-02 15:46:41 +0100163 }
164
Michal Vasko81b33fb2016-09-26 14:57:36 +0200165 } while (readd < count);
166 buf[count] = '\0';
Radek Krejci206fcd62015-10-07 15:42:48 +0200167
Michal Vasko81b33fb2016-09-26 14:57:36 +0200168 return (ssize_t)readd;
Radek Krejci206fcd62015-10-07 15:42:48 +0200169}
170
171static ssize_t
Michal Vasko36c7be82017-02-22 13:37:59 +0100172nc_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 +0200173{
174 ssize_t r;
175
176 assert(session);
177 assert(chunk);
178
179 if (!len) {
180 return 0;
181 }
182
Michal Vasko4eb3c312016-03-01 14:09:37 +0100183 *chunk = malloc((len + 1) * sizeof **chunk);
roman3a95bb22023-10-26 11:07:17 +0200184 NC_CHECK_ERRMEM_RET(!*chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200185
Michal Vasko36c7be82017-02-22 13:37:59 +0100186 r = nc_read(session, *chunk, len, inact_timeout, ts_act_timeout);
Radek Krejci206fcd62015-10-07 15:42:48 +0200187 if (r <= 0) {
188 free(*chunk);
189 return -1;
190 }
191
192 /* terminating null byte */
Radek Krejcife0b3472015-10-12 13:43:42 +0200193 (*chunk)[r] = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200194
195 return r;
196}
197
198static ssize_t
Michal Vasko36c7be82017-02-22 13:37:59 +0100199nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint32_t inact_timeout,
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200200 struct timespec *ts_act_timeout, char **result)
Radek Krejci206fcd62015-10-07 15:42:48 +0200201{
202 char *chunk = NULL;
David Sedlákfedbc792018-07-04 11:07:07 +0200203 size_t size, count = 0, r, len, i, matched = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200204
205 assert(session);
206 assert(endtag);
207
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200208 if (limit && (limit < BUFFERSIZE)) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200209 size = limit;
210 } else {
211 size = BUFFERSIZE;
212 }
Michal Vasko4eb3c312016-03-01 14:09:37 +0100213 chunk = malloc((size + 1) * sizeof *chunk);
roman3a95bb22023-10-26 11:07:17 +0200214 NC_CHECK_ERRMEM_RET(!chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200215
216 len = strlen(endtag);
Michal Vasko428087d2016-01-14 16:04:28 +0100217 while (1) {
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200218 if (limit && (count == limit)) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200219 free(chunk);
Michal Vasko05532772021-06-03 12:12:38 +0200220 WRN(session, "Reading limit (%d) reached.", limit);
221 ERR(session, "Invalid input data (missing \"%s\" sequence).", endtag);
Radek Krejci206fcd62015-10-07 15:42:48 +0200222 return -1;
223 }
224
225 /* resize buffer if needed */
David Sedlákfedbc792018-07-04 11:07:07 +0200226 if ((count + (len - matched)) >= size) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200227 /* get more memory */
228 size = size + BUFFERSIZE;
Radek Krejcif6d9aef2018-08-17 11:50:53 +0200229 chunk = nc_realloc(chunk, (size + 1) * sizeof *chunk);
roman3a95bb22023-10-26 11:07:17 +0200230 NC_CHECK_ERRMEM_RET(!chunk, -1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200231 }
232
233 /* get another character */
David Sedlákfedbc792018-07-04 11:07:07 +0200234 r = nc_read(session, &(chunk[count]), len - matched, inact_timeout, ts_act_timeout);
235 if (r != len - matched) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200236 free(chunk);
237 return -1;
238 }
239
David Sedlákfedbc792018-07-04 11:07:07 +0200240 count += len - matched;
Radek Krejci206fcd62015-10-07 15:42:48 +0200241
David Sedlákfedbc792018-07-04 11:07:07 +0200242 for (i = len - matched; i > 0; i--) {
243 if (!strncmp(&endtag[matched], &(chunk[count - i]), i)) {
244 /*part of endtag found */
245 matched += i;
Radek Krejci206fcd62015-10-07 15:42:48 +0200246 break;
David Sedlákfedbc792018-07-04 11:07:07 +0200247 } else {
248 matched = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200249 }
250 }
David Sedlákfedbc792018-07-04 11:07:07 +0200251
252 /* whole endtag found */
253 if (matched == len) {
254 break;
255 }
Radek Krejci206fcd62015-10-07 15:42:48 +0200256 }
257
258 /* terminating null byte */
259 chunk[count] = 0;
260
261 if (result) {
262 *result = chunk;
Radek Krejcife0b3472015-10-12 13:43:42 +0200263 } else {
264 free(chunk);
Radek Krejci206fcd62015-10-07 15:42:48 +0200265 }
266 return count;
267}
268
Michal Vasko77367452021-02-16 16:32:18 +0100269int
270nc_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 +0100271{
Michal Vasko77367452021-02-16 16:32:18 +0100272 int ret = 1, r, io_locked = passing_io_lock;
273 char *data = NULL, *chunk;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100274 uint64_t chunk_len, len = 0;
Michal Vasko36c7be82017-02-22 13:37:59 +0100275 /* use timeout in milliseconds instead seconds */
276 uint32_t inact_timeout = NC_READ_INACT_TIMEOUT * 1000;
277 struct timespec ts_act_timeout;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100278
Michal Vasko77367452021-02-16 16:32:18 +0100279 assert(session && msg);
280 *msg = NULL;
Michal Vasko428087d2016-01-14 16:04:28 +0100281
282 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200283 ERR(session, "Invalid session to read from.");
Michal Vasko77367452021-02-16 16:32:18 +0100284 ret = -1;
Michal Vasko131120a2018-05-29 15:44:02 +0200285 goto cleanup;
Michal Vasko428087d2016-01-14 16:04:28 +0100286 }
287
Michal Vaskod8a74192023-02-06 15:51:50 +0100288 nc_timeouttime_get(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000);
Michal Vasko36c7be82017-02-22 13:37:59 +0100289
Michal Vasko131120a2018-05-29 15:44:02 +0200290 if (!io_locked) {
291 /* SESSION IO LOCK */
292 ret = nc_session_io_lock(session, io_timeout, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100293 if (ret < 1) {
Michal Vasko131120a2018-05-29 15:44:02 +0200294 goto cleanup;
295 }
296 io_locked = 1;
297 }
298
Michal Vasko05ba9df2016-01-13 14:40:27 +0100299 /* read the message */
300 switch (session->version) {
301 case NC_VERSION_10:
Michal Vasko77367452021-02-16 16:32:18 +0100302 r = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, inact_timeout, &ts_act_timeout, &data);
303 if (r == -1) {
304 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200305 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100306 }
307
308 /* cut off the end tag */
Michal Vasko77367452021-02-16 16:32:18 +0100309 data[r - NC_VERSION_10_ENDTAG_LEN] = '\0';
Michal Vasko05ba9df2016-01-13 14:40:27 +0100310 break;
311 case NC_VERSION_11:
312 while (1) {
Michal Vasko77367452021-02-16 16:32:18 +0100313 r = nc_read_until(session, "\n#", 0, inact_timeout, &ts_act_timeout, NULL);
314 if (r == -1) {
315 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200316 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100317 }
Michal Vasko77367452021-02-16 16:32:18 +0100318 r = nc_read_until(session, "\n", 0, inact_timeout, &ts_act_timeout, &chunk);
319 if (r == -1) {
320 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200321 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100322 }
323
324 if (!strcmp(chunk, "#\n")) {
325 /* end of chunked framing message */
326 free(chunk);
Michal Vasko77367452021-02-16 16:32:18 +0100327 if (!data) {
Michal Vasko05532772021-06-03 12:12:38 +0200328 ERR(session, "Invalid frame chunk delimiters.");
Michal Vasko77367452021-02-16 16:32:18 +0100329 ret = -2;
330 goto cleanup;
Michal Vasko79df3262016-07-13 13:42:17 +0200331 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100332 break;
333 }
334
335 /* convert string to the size of the following chunk */
336 chunk_len = strtoul(chunk, (char **)NULL, 10);
337 free(chunk);
338 if (!chunk_len) {
Michal Vasko05532772021-06-03 12:12:38 +0200339 ERR(session, "Invalid frame chunk size detected, fatal error.");
Michal Vasko77367452021-02-16 16:32:18 +0100340 ret = -2;
341 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100342 }
343
344 /* now we have size of next chunk, so read the chunk */
Michal Vasko77367452021-02-16 16:32:18 +0100345 r = nc_read_chunk(session, chunk_len, inact_timeout, &ts_act_timeout, &chunk);
346 if (r == -1) {
347 ret = r;
Michal Vasko131120a2018-05-29 15:44:02 +0200348 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100349 }
350
351 /* realloc message buffer, remember to count terminating null byte */
Michal Vasko77367452021-02-16 16:32:18 +0100352 data = nc_realloc(data, len + chunk_len + 1);
roman3a95bb22023-10-26 11:07:17 +0200353 NC_CHECK_ERRMEM_GOTO(!data, ret = -1, cleanup);
Michal Vasko77367452021-02-16 16:32:18 +0100354 memcpy(data + len, chunk, chunk_len);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100355 len += chunk_len;
Michal Vasko77367452021-02-16 16:32:18 +0100356 data[len] = '\0';
Michal Vasko05ba9df2016-01-13 14:40:27 +0100357 free(chunk);
358 }
359
360 break;
361 }
Michal Vasko131120a2018-05-29 15:44:02 +0200362
363 /* SESSION IO UNLOCK */
364 assert(io_locked);
365 nc_session_io_unlock(session, __func__);
366 io_locked = 0;
367
Michal Vasko05532772021-06-03 12:12:38 +0200368 DBG(session, "Received message:\n%s\n", data);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100369
Michal Vasko77367452021-02-16 16:32:18 +0100370 /* build an input structure, eats data */
371 if (ly_in_new_memory(data, msg)) {
372 ret = -1;
373 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100374 }
Michal Vasko77367452021-02-16 16:32:18 +0100375 data = NULL;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100376
Michal Vasko131120a2018-05-29 15:44:02 +0200377cleanup:
378 if (io_locked) {
Michal Vasko77367452021-02-16 16:32:18 +0100379 /* SESSION IO UNLOCK */
Michal Vasko131120a2018-05-29 15:44:02 +0200380 nc_session_io_unlock(session, __func__);
381 }
Michal Vasko77367452021-02-16 16:32:18 +0100382 free(data);
Michal Vasko131120a2018-05-29 15:44:02 +0200383 return ret;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100384}
385
Michal Vasko428087d2016-01-14 16:04:28 +0100386/* return -1 means either poll error or that session was invalidated (socket error), EINTR is handled inside */
387static int
Michal Vasko131120a2018-05-29 15:44:02 +0200388nc_read_poll(struct nc_session *session, int io_timeout)
Michal Vasko428087d2016-01-14 16:04:28 +0100389{
390 int ret = -2;
391 struct pollfd fds;
Michal Vasko428087d2016-01-14 16:04:28 +0100392
393 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200394 ERR(session, "Invalid session to poll.");
Michal Vasko428087d2016-01-14 16:04:28 +0100395 return -1;
396 }
397
398 switch (session->ti_type) {
roman2eab4742023-06-06 10:00:26 +0200399#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200400 case NC_TI_SSH:
Michal Vasko428087d2016-01-14 16:04:28 +0100401 /* EINTR is handled, it resumes waiting */
Michal Vasko131120a2018-05-29 15:44:02 +0200402 ret = ssh_channel_poll_timeout(session->ti.libssh.channel, io_timeout, 0);
Michal Vasko428087d2016-01-14 16:04:28 +0100403 if (ret == SSH_ERROR) {
Michal Vasko05532772021-06-03 12:12:38 +0200404 ERR(session, "SSH channel poll error (%s).", ssh_get_error(session->ti.libssh.session));
Michal Vasko428087d2016-01-14 16:04:28 +0100405 session->status = NC_STATUS_INVALID;
406 session->term_reason = NC_SESSION_TERM_OTHER;
407 return -1;
408 } else if (ret == SSH_EOF) {
Michal Vasko05532772021-06-03 12:12:38 +0200409 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko428087d2016-01-14 16:04:28 +0100410 session->status = NC_STATUS_INVALID;
411 session->term_reason = NC_SESSION_TERM_DROPPED;
412 return -1;
413 } else if (ret > 0) {
414 /* fake it */
415 ret = 1;
416 fds.revents = POLLIN;
Michal Vasko5550cda2016-02-03 15:28:57 +0100417 } else { /* ret == 0 */
418 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100419 }
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100420 break;
roman506354a2024-04-11 09:37:22 +0200421 case NC_TI_TLS:
roman0fa69ee2024-04-23 15:11:02 +0200422 ret = nc_tls_get_num_pending_bytes_wrap(session->ti.tls.session);
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100423 if (ret) {
424 /* some buffered TLS data available */
425 ret = 1;
426 fds.revents = POLLIN;
427 break;
Michal Vasko428087d2016-01-14 16:04:28 +0100428 }
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100429
romana2481092024-04-05 12:30:22 +0200430 fds.fd = nc_tls_get_fd_wrap(session);
roman2eab4742023-06-06 10:00:26 +0200431#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200432 /* fallthrough */
Michal Vasko428087d2016-01-14 16:04:28 +0100433 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200434 case NC_TI_UNIX:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200435 if (session->ti_type == NC_TI_FD) {
Michal Vasko428087d2016-01-14 16:04:28 +0100436 fds.fd = session->ti.fd.in;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200437 } else if (session->ti_type == NC_TI_UNIX) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200438 fds.fd = session->ti.unixsock.sock;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200439 }
Michal Vasko428087d2016-01-14 16:04:28 +0100440
Michal Vaskob5a58fa2017-01-31 09:47:50 +0100441 fds.events = POLLIN;
442 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100443
Michal Vasko63b92d62024-04-29 10:04:56 +0200444 ret = nc_poll(&fds, 1, io_timeout);
Michal Vasko428087d2016-01-14 16:04:28 +0100445 break;
446
447 default:
448 ERRINT;
449 return -1;
450 }
451
452 /* process the poll result, unified ret meaning for poll and ssh_channel poll */
453 if (ret < 0) {
454 /* poll failed - something really bad happened, close the session */
Michal Vasko05532772021-06-03 12:12:38 +0200455 ERR(session, "poll error (%s).", strerror(errno));
Michal Vasko428087d2016-01-14 16:04:28 +0100456 session->status = NC_STATUS_INVALID;
457 session->term_reason = NC_SESSION_TERM_OTHER;
458 return -1;
459 } else { /* status > 0 */
460 /* in case of standard (non-libssh) poll, there still can be an error */
Michal Vasko428087d2016-01-14 16:04:28 +0100461 if (fds.revents & POLLERR) {
Michal Vasko05532772021-06-03 12:12:38 +0200462 ERR(session, "Communication channel error.");
Michal Vasko428087d2016-01-14 16:04:28 +0100463 session->status = NC_STATUS_INVALID;
464 session->term_reason = NC_SESSION_TERM_OTHER;
465 return -1;
466 }
Robin Jarryf732adc2020-05-15 11:18:38 +0200467 /* Some poll() implementations may return POLLHUP|POLLIN when the other
468 * side has closed but there is data left to read in the buffer. */
469 if ((fds.revents & POLLHUP) && !(fds.revents & POLLIN)) {
Michal Vasko05532772021-06-03 12:12:38 +0200470 ERR(session, "Communication channel unexpectedly closed.");
Robin Jarryf732adc2020-05-15 11:18:38 +0200471 session->status = NC_STATUS_INVALID;
472 session->term_reason = NC_SESSION_TERM_DROPPED;
473 return -1;
474 }
Michal Vasko428087d2016-01-14 16:04:28 +0100475 }
476
477 return ret;
478}
479
Michal Vasko77367452021-02-16 16:32:18 +0100480int
481nc_read_msg_poll_io(struct nc_session *session, int io_timeout, struct ly_in **msg)
Radek Krejci206fcd62015-10-07 15:42:48 +0200482{
Michal Vasko428087d2016-01-14 16:04:28 +0100483 int ret;
Radek Krejci206fcd62015-10-07 15:42:48 +0200484
Michal Vasko77367452021-02-16 16:32:18 +0100485 assert(msg);
486 *msg = NULL;
Radek Krejci206fcd62015-10-07 15:42:48 +0200487
Michal Vasko428087d2016-01-14 16:04:28 +0100488 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200489 ERR(session, "Invalid session to read from.");
Michal Vasko77367452021-02-16 16:32:18 +0100490 return -1;
Radek Krejci206fcd62015-10-07 15:42:48 +0200491 }
492
Michal Vasko131120a2018-05-29 15:44:02 +0200493 /* SESSION IO LOCK */
494 ret = nc_session_io_lock(session, io_timeout, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100495 if (ret < 1) {
496 return ret;
Michal Vasko131120a2018-05-29 15:44:02 +0200497 }
498
499 ret = nc_read_poll(session, io_timeout);
Michal Vasko77367452021-02-16 16:32:18 +0100500 if (ret < 1) {
501 /* timed out or error */
Michal Vasko131120a2018-05-29 15:44:02 +0200502
503 /* SESSION IO UNLOCK */
504 nc_session_io_unlock(session, __func__);
Michal Vasko77367452021-02-16 16:32:18 +0100505 return ret;
Radek Krejci206fcd62015-10-07 15:42:48 +0200506 }
507
Michal Vasko131120a2018-05-29 15:44:02 +0200508 /* SESSION IO LOCK passed down */
Michal Vasko77367452021-02-16 16:32:18 +0100509 return nc_read_msg_io(session, io_timeout, msg, 1);
Radek Krejci206fcd62015-10-07 15:42:48 +0200510}
Radek Krejcife0b3472015-10-12 13:43:42 +0200511
Michal Vasko428087d2016-01-14 16:04:28 +0100512/* does not really log, only fatal errors */
513int
romane5675b12024-03-05 14:26:23 +0100514nc_session_is_connected(const struct nc_session *session)
Michal Vasko428087d2016-01-14 16:04:28 +0100515{
516 int ret;
517 struct pollfd fds;
518
519 switch (session->ti_type) {
520 case NC_TI_FD:
521 fds.fd = session->ti.fd.in;
522 break;
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200523 case NC_TI_UNIX:
524 fds.fd = session->ti.unixsock.sock;
525 break;
roman2eab4742023-06-06 10:00:26 +0200526#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200527 case NC_TI_SSH:
Michal Vasko840a8a62017-02-07 10:56:34 +0100528 return ssh_is_connected(session->ti.libssh.session);
roman506354a2024-04-11 09:37:22 +0200529 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200530 fds.fd = nc_tls_get_fd_wrap(session);
Michal Vasko428087d2016-01-14 16:04:28 +0100531 break;
roman2eab4742023-06-06 10:00:26 +0200532#endif /* NC_ENABLED_SSH_TLS */
Michal Vaskof945da52018-02-15 08:45:13 +0100533 default:
Michal Vasko428087d2016-01-14 16:04:28 +0100534 return 0;
535 }
536
Michal Vasko840a8a62017-02-07 10:56:34 +0100537 if (fds.fd == -1) {
538 return 0;
539 }
540
Michal Vasko428087d2016-01-14 16:04:28 +0100541 fds.events = POLLIN;
Michal Vasko3e9d1682017-02-24 09:50:15 +0100542 fds.revents = 0;
Michal Vasko428087d2016-01-14 16:04:28 +0100543
Michal Vasko63b92d62024-04-29 10:04:56 +0200544 ret = nc_poll(&fds, 1, 0);
Michal Vasko428087d2016-01-14 16:04:28 +0100545 if (ret == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100546 return 0;
547 } else if ((ret > 0) && (fds.revents & (POLLHUP | POLLERR))) {
548 return 0;
549 }
550
551 return 1;
552}
553
Radek Krejcife0b3472015-10-12 13:43:42 +0200554#define WRITE_BUFSIZE (2 * BUFFERSIZE)
Michal Vasko3a8654b2024-05-13 09:43:49 +0200555struct nc_wclb_arg {
Radek Krejcife0b3472015-10-12 13:43:42 +0200556 struct nc_session *session;
557 char buf[WRITE_BUFSIZE];
Michal Vasko3a8654b2024-05-13 09:43:49 +0200558 uint32_t len;
Radek Krejcife0b3472015-10-12 13:43:42 +0200559};
560
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200561/**
562 * @brief Write to a NETCONF session.
563 *
564 * @param[in] session Session to write to.
565 * @param[in] buf Buffer to write.
566 * @param[in] count Count of bytes from @p buf to write.
567 * @return Number of bytes written.
568 * @return -1 on error.
569 */
Michal Vasko964e1732016-09-23 13:39:33 +0200570static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200571nc_write(struct nc_session *session, const void *buf, uint32_t count)
Radek Krejcife0b3472015-10-12 13:43:42 +0200572{
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200573 int c, fd, interrupted;
Michal Vasko3a8654b2024-05-13 09:43:49 +0200574 uint32_t written = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200575
Michal Vasko428087d2016-01-14 16:04:28 +0100576 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
577 return -1;
578 }
579
580 /* prevent SIGPIPE this way */
581 if (!nc_session_is_connected(session)) {
Michal Vasko05532772021-06-03 12:12:38 +0200582 ERR(session, "Communication socket unexpectedly closed.");
Michal Vasko2a7d4732016-01-15 09:24:46 +0100583 session->status = NC_STATUS_INVALID;
584 session->term_reason = NC_SESSION_TERM_DROPPED;
Michal Vasko428087d2016-01-14 16:04:28 +0100585 return -1;
586 }
587
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200588 DBG(session, "Sending message:\n%.*s\n", (int)count, buf);
Michal Vasko160b7912016-06-20 10:00:53 +0200589
Michal Vasko81b33fb2016-09-26 14:57:36 +0200590 do {
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200591 interrupted = 0;
Michal Vasko964e1732016-09-23 13:39:33 +0200592 switch (session->ti_type) {
Michal Vasko964e1732016-09-23 13:39:33 +0200593 case NC_TI_FD:
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200594 case NC_TI_UNIX:
595 fd = session->ti_type == NC_TI_FD ? session->ti.fd.out : session->ti.unixsock.sock;
596 c = write(fd, (char *)(buf + written), count - written);
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200597 if ((c < 0) && (errno == EAGAIN)) {
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200598 c = 0;
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200599 } else if ((c < 0) && (errno == EINTR)) {
Robin Jarry7de4b8e2019-10-14 21:46:00 +0200600 c = 0;
601 interrupted = 1;
Olivier Matzac7fa2f2018-10-11 10:02:04 +0200602 } else if (c < 0) {
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200603 ERR(session, "Socket error (%s).", strerror(errno));
Michal Vasko964e1732016-09-23 13:39:33 +0200604 return -1;
605 }
606 break;
Radek Krejcife0b3472015-10-12 13:43:42 +0200607
roman2eab4742023-06-06 10:00:26 +0200608#ifdef NC_ENABLED_SSH_TLS
roman506354a2024-04-11 09:37:22 +0200609 case NC_TI_SSH:
Michal Vasko964e1732016-09-23 13:39:33 +0200610 if (ssh_channel_is_closed(session->ti.libssh.channel) || ssh_channel_is_eof(session->ti.libssh.channel)) {
611 if (ssh_channel_is_closed(session->ti.libssh.channel)) {
Michal Vasko05532772021-06-03 12:12:38 +0200612 ERR(session, "SSH channel unexpectedly closed.");
Michal Vasko964e1732016-09-23 13:39:33 +0200613 } else {
Michal Vasko05532772021-06-03 12:12:38 +0200614 ERR(session, "SSH channel unexpected EOF.");
Michal Vasko964e1732016-09-23 13:39:33 +0200615 }
616 session->status = NC_STATUS_INVALID;
617 session->term_reason = NC_SESSION_TERM_DROPPED;
618 return -1;
Michal Vasko454e22b2016-01-21 15:34:08 +0100619 }
Michal Vasko81b33fb2016-09-26 14:57:36 +0200620 c = ssh_channel_write(session->ti.libssh.channel, (char *)(buf + written), count - written);
Michal Vasko964e1732016-09-23 13:39:33 +0200621 if ((c == SSH_ERROR) || (c == -1)) {
Michal Vasko05532772021-06-03 12:12:38 +0200622 ERR(session, "SSH channel write failed.");
Michal Vasko964e1732016-09-23 13:39:33 +0200623 return -1;
624 }
625 break;
roman506354a2024-04-11 09:37:22 +0200626 case NC_TI_TLS:
romana2481092024-04-05 12:30:22 +0200627 c = nc_tls_write_wrap(session, (const unsigned char *)(buf + written), count - written);
628 if (c < 0) {
629 /* possible client dc, or some socket/TLS communication error */
630 return -1;
Michal Vasko964e1732016-09-23 13:39:33 +0200631 }
632 break;
roman2eab4742023-06-06 10:00:26 +0200633#endif /* NC_ENABLED_SSH_TLS */
Michal Vasko339eea82016-09-29 11:42:36 +0200634 default:
635 ERRINT;
636 return -1;
Michal Vasko964e1732016-09-23 13:39:33 +0200637 }
638
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200639 if ((c == 0) && !interrupted) {
Michal Vasko964e1732016-09-23 13:39:33 +0200640 /* we must wait */
641 usleep(NC_TIMEOUT_STEP);
642 }
643
644 written += c;
Michal Vasko81b33fb2016-09-26 14:57:36 +0200645 } while (written < count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200646
Michal Vasko964e1732016-09-23 13:39:33 +0200647 return written;
Radek Krejcife0b3472015-10-12 13:43:42 +0200648}
649
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200650/**
651 * @brief Write the start tag and the message part of a chunked-framing NETCONF message.
652 *
653 * @param[in] session Session to write to.
654 * @param[in] buf Message buffer to write.
655 * @param[in] count Count of bytes from @p buf to write.
656 * @return Number of bytes written.
657 * @return -1 on error.
658 */
Michal Vasko428087d2016-01-14 16:04:28 +0100659static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200660nc_write_starttag_and_msg(struct nc_session *session, const void *buf, uint32_t count)
Michal Vasko086311b2016-01-08 09:53:11 +0100661{
Michal Vasko3a8654b2024-05-13 09:43:49 +0200662 int ret = 0, r;
Claus Klein22091912020-01-20 13:45:47 +0100663 char chunksize[24];
Michal Vasko086311b2016-01-08 09:53:11 +0100664
665 if (session->version == NC_VERSION_11) {
Michal Vasko3a8654b2024-05-13 09:43:49 +0200666 r = sprintf(chunksize, "\n#%" PRIu32 "\n", count);
667
668 r = nc_write(session, chunksize, r);
669 if (r == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100670 return -1;
671 }
Michal Vasko3a8654b2024-05-13 09:43:49 +0200672 ret += r;
Michal Vasko086311b2016-01-08 09:53:11 +0100673 }
Michal Vasko428087d2016-01-14 16:04:28 +0100674
Michal Vasko3a8654b2024-05-13 09:43:49 +0200675 r = nc_write(session, buf, count);
676 if (r == -1) {
Michal Vasko428087d2016-01-14 16:04:28 +0100677 return -1;
678 }
Michal Vasko3a8654b2024-05-13 09:43:49 +0200679 ret += r;
Michal Vasko428087d2016-01-14 16:04:28 +0100680
681 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100682}
683
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200684/**
685 * @brief Write the end tag part of a chunked-framing NETCONF message.
686 *
687 * @param[in] session Session to write to.
688 * @return Number of bytes written.
689 * @return -1 on error.
690 */
Radek Krejcife0b3472015-10-12 13:43:42 +0200691static int
Michal Vasko428087d2016-01-14 16:04:28 +0100692nc_write_endtag(struct nc_session *session)
Radek Krejcife0b3472015-10-12 13:43:42 +0200693{
Michal Vasko428087d2016-01-14 16:04:28 +0100694 int ret;
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100695
Michal Vasko428087d2016-01-14 16:04:28 +0100696 if (session->version == NC_VERSION_11) {
697 ret = nc_write(session, "\n##\n", 4);
698 } else {
699 ret = nc_write(session, "]]>]]>", 6);
Radek Krejcife0b3472015-10-12 13:43:42 +0200700 }
701
Michal Vasko428087d2016-01-14 16:04:28 +0100702 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200703}
704
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200705/**
706 * @brief Flush all the data buffered for writing.
707 *
708 * @param[in] warg Write callback structure to flush.
709 * @return Number of written bytes.
710 * @return -1 on error.
711 */
Michal Vasko428087d2016-01-14 16:04:28 +0100712static int
Michal Vasko3a8654b2024-05-13 09:43:49 +0200713nc_write_clb_flush(struct nc_wclb_arg *warg)
Radek Krejcife0b3472015-10-12 13:43:42 +0200714{
Michal Vasko428087d2016-01-14 16:04:28 +0100715 int ret = 0;
716
Radek Krejcife0b3472015-10-12 13:43:42 +0200717 /* flush current buffer */
718 if (warg->len) {
Michal Vasko428087d2016-01-14 16:04:28 +0100719 ret = nc_write_starttag_and_msg(warg->session, warg->buf, warg->len);
Radek Krejcife0b3472015-10-12 13:43:42 +0200720 warg->len = 0;
721 }
Michal Vasko428087d2016-01-14 16:04:28 +0100722
723 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200724}
725
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200726/**
727 * @brief Write callback buffering the data in a write structure.
728 *
729 * @param[in] arg Write structure used for buffering.
730 * @param[in] buf Buffer to write.
731 * @param[in] count Count of bytes to write from @p buf.
732 * @param[in] xmlcontent Whether the data are actually printed as part of an XML in which case they need to be encoded.
733 * @return Number of written bytes.
734 * @return -1 on error.
735 */
Radek Krejcife0b3472015-10-12 13:43:42 +0200736static ssize_t
Michal Vasko3a8654b2024-05-13 09:43:49 +0200737nc_write_clb(void *arg, const void *buf, uint32_t count, int xmlcontent)
Radek Krejcife0b3472015-10-12 13:43:42 +0200738{
Michal Vasko3a8654b2024-05-13 09:43:49 +0200739 ssize_t ret = 0, c;
740 uint32_t l;
741 struct nc_wclb_arg *warg = arg;
Radek Krejcife0b3472015-10-12 13:43:42 +0200742
743 if (!buf) {
Michal Vasko428087d2016-01-14 16:04:28 +0100744 c = nc_write_clb_flush(warg);
745 if (c == -1) {
746 return -1;
747 }
748 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200749
750 /* endtag */
Michal Vasko428087d2016-01-14 16:04:28 +0100751 c = nc_write_endtag(warg->session);
752 if (c == -1) {
753 return -1;
754 }
755 ret += c;
756
757 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200758 }
759
760 if (warg->len && (warg->len + count > WRITE_BUFSIZE)) {
761 /* dump current buffer */
Michal Vasko428087d2016-01-14 16:04:28 +0100762 c = nc_write_clb_flush(warg);
763 if (c == -1) {
764 return -1;
765 }
766 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200767 }
Michal Vasko428087d2016-01-14 16:04:28 +0100768
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200769 if (!xmlcontent && (count > WRITE_BUFSIZE)) {
Radek Krejcife0b3472015-10-12 13:43:42 +0200770 /* write directly */
Michal Vasko428087d2016-01-14 16:04:28 +0100771 c = nc_write_starttag_and_msg(warg->session, buf, count);
772 if (c == -1) {
773 return -1;
774 }
775 ret += c;
Radek Krejcife0b3472015-10-12 13:43:42 +0200776 } else {
777 /* keep in buffer and write later */
Radek Krejci047300e2016-03-08 16:46:58 +0100778 if (xmlcontent) {
779 for (l = 0; l < count; l++) {
780 if (warg->len + 5 >= WRITE_BUFSIZE) {
781 /* buffer is full */
782 c = nc_write_clb_flush(warg);
783 if (c == -1) {
784 return -1;
785 }
786 }
787
788 switch (((char *)buf)[l]) {
789 case '&':
790 ret += 5;
791 memcpy(&warg->buf[warg->len], "&amp;", 5);
792 warg->len += 5;
793 break;
794 case '<':
795 ret += 4;
796 memcpy(&warg->buf[warg->len], "&lt;", 4);
797 warg->len += 4;
798 break;
799 case '>':
800 /* not needed, just for readability */
801 ret += 4;
802 memcpy(&warg->buf[warg->len], "&gt;", 4);
803 warg->len += 4;
804 break;
805 default:
806 ret++;
807 memcpy(&warg->buf[warg->len], &((char *)buf)[l], 1);
808 warg->len++;
809 }
810 }
811 } else {
812 memcpy(&warg->buf[warg->len], buf, count);
813 warg->len += count; /* is <= WRITE_BUFSIZE */
814 ret += count;
815 }
Radek Krejcife0b3472015-10-12 13:43:42 +0200816 }
817
Michal Vasko428087d2016-01-14 16:04:28 +0100818 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +0200819}
820
Michal Vaskoa82e1a12024-05-13 09:43:19 +0200821/**
822 * @brief Write print callback used by libyang.
823 */
Radek Krejci047300e2016-03-08 16:46:58 +0100824static ssize_t
825nc_write_xmlclb(void *arg, const void *buf, size_t count)
826{
Michal Vasko77367452021-02-16 16:32:18 +0100827 ssize_t r;
Radek Krejci047300e2016-03-08 16:46:58 +0100828
Michal Vasko77367452021-02-16 16:32:18 +0100829 r = nc_write_clb(arg, buf, count, 0);
830 if (r == -1) {
831 return -1;
Michal Vasko08611b32016-12-05 13:30:37 +0100832 }
833
Michal Vasko77367452021-02-16 16:32:18 +0100834 /* always return what libyang expects, simply that all the characters were printed */
835 return count;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100836}
837
Michal Vasko131120a2018-05-29 15:44:02 +0200838/* return NC_MSG_ERROR can change session status, acquires IO lock as needed */
839NC_MSG_TYPE
840nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
Radek Krejcife0b3472015-10-12 13:43:42 +0200841{
Radek Krejcid116db42016-01-08 15:36:30 +0100842 va_list ap;
Michal Vasko131120a2018-05-29 15:44:02 +0200843 int count, ret;
Michal Vasko70bf2222021-10-26 10:45:15 +0200844 const char *attrs, *str;
Michal Vaskoff7286b2021-07-09 13:13:11 +0200845 struct lyd_node *op, *reply_envp, *node, *next;
Michal Vasko77367452021-02-16 16:32:18 +0100846 struct lyd_node_opaq *rpc_envp;
Radek Krejci93e80222016-10-03 13:34:25 +0200847 struct nc_server_notif *notif;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100848 struct nc_server_reply *reply;
Michal Vasko77367452021-02-16 16:32:18 +0100849 char *buf;
Michal Vasko3a8654b2024-05-13 09:43:49 +0200850 struct nc_wclb_arg arg;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200851 const char **capabilities;
Michal Vasko77367452021-02-16 16:32:18 +0100852 uint32_t *sid = NULL, i, wd = 0;
853 LY_ERR lyrc;
Radek Krejcife0b3472015-10-12 13:43:42 +0200854
Michal Vasko428087d2016-01-14 16:04:28 +0100855 assert(session);
856
857 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
Michal Vasko05532772021-06-03 12:12:38 +0200858 ERR(session, "Invalid session to write to.");
Michal Vasko131120a2018-05-29 15:44:02 +0200859 return NC_MSG_ERROR;
Michal Vasko428087d2016-01-14 16:04:28 +0100860 }
861
Radek Krejcife0b3472015-10-12 13:43:42 +0200862 arg.session = session;
863 arg.len = 0;
864
Michal Vasko131120a2018-05-29 15:44:02 +0200865 /* SESSION IO LOCK */
866 ret = nc_session_io_lock(session, io_timeout, __func__);
867 if (ret < 0) {
868 return NC_MSG_ERROR;
869 } else if (!ret) {
870 return NC_MSG_WOULDBLOCK;
871 }
872
873 va_start(ap, type);
Radek Krejci127f8952016-10-12 14:57:16 +0200874
Radek Krejcife0b3472015-10-12 13:43:42 +0200875 switch (type) {
876 case NC_MSG_RPC:
Michal Vasko77367452021-02-16 16:32:18 +0100877 op = va_arg(ap, struct lyd_node *);
Radek Krejcife0b3472015-10-12 13:43:42 +0200878 attrs = va_arg(ap, const char *);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100879
Michal Vasko70bf2222021-10-26 10:45:15 +0200880 /* <rpc> open */
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200881 count = asprintf(&buf, "<rpc xmlns=\"%s\" message-id=\"%" PRIu64 "\"%s>",
Michal Vasko77367452021-02-16 16:32:18 +0100882 NC_NS_BASE, session->opts.client.msgid + 1, attrs ? attrs : "");
roman3a95bb22023-10-26 11:07:17 +0200883 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +0100884 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +0200885 free(buf);
Michal Vaskoe1708602016-10-18 12:17:22 +0200886
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100887 if (op->schema && (op->schema->nodetype & (LYS_CONTAINER | LYS_LIST))) {
Michal Vasko70bf2222021-10-26 10:45:15 +0200888 /* <action> open */
889 str = "<action xmlns=\"urn:ietf:params:xml:ns:yang:1\">";
890 nc_write_clb((void *)&arg, str, strlen(str), 0);
891 }
892
893 /* rpc data */
Michal Vasko6b70b822022-01-21 08:39:16 +0100894 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 +0200895 ret = NC_MSG_ERROR;
896 goto cleanup;
Michal Vasko5a91ce72017-10-19 11:30:02 +0200897 }
Michal Vasko70bf2222021-10-26 10:45:15 +0200898
Michal Vasko7e1f5fb2021-11-10 10:14:45 +0100899 if (op->schema && (op->schema->nodetype & (LYS_CONTAINER | LYS_LIST))) {
Michal Vasko70bf2222021-10-26 10:45:15 +0200900 /* <action> close */
901 str = "</action>";
902 nc_write_clb((void *)&arg, str, strlen(str), 0);
903 }
904
905 /* <rpc> close */
906 str = "</rpc>";
907 nc_write_clb((void *)&arg, str, strlen(str), 0);
Radek Krejcife0b3472015-10-12 13:43:42 +0200908
Michal Vasko2e6defd2016-10-07 15:48:15 +0200909 session->opts.client.msgid++;
Radek Krejcife0b3472015-10-12 13:43:42 +0200910 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100911
Radek Krejcife0b3472015-10-12 13:43:42 +0200912 case NC_MSG_REPLY:
Michal Vasko77367452021-02-16 16:32:18 +0100913 rpc_envp = va_arg(ap, struct lyd_node_opaq *);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100914 reply = va_arg(ap, struct nc_server_reply *);
915
Michal Vasko77367452021-02-16 16:32:18 +0100916 if (!rpc_envp) {
917 /* can be NULL if replying with a malformed-message error */
918 nc_write_clb((void *)&arg, "<rpc-reply xmlns=\"" NC_NS_BASE "\">", 18 + strlen(NC_NS_BASE) + 2, 0);
919
920 assert(reply->type == NC_RPL_ERROR);
921 if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, ((struct nc_server_reply_error *)reply)->err, LYD_XML,
922 LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)) {
923 ret = NC_MSG_ERROR;
924 goto cleanup;
925 }
926
927 nc_write_clb((void *)&arg, "</rpc-reply>", 12, 0);
928 break;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +0100929 }
930
Michal Vasko77367452021-02-16 16:32:18 +0100931 /* build a rpc-reply opaque node that can be simply printed */
Michal Vaskoff7286b2021-07-09 13:13:11 +0200932 if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns,
933 &reply_envp)) {
Michal Vasko77367452021-02-16 16:32:18 +0100934 ERRINT;
935 ret = NC_MSG_ERROR;
936 goto cleanup;
Michal Vaskoe7e534f2016-01-15 09:51:09 +0100937 }
Michal Vasko77367452021-02-16 16:32:18 +0100938
Michal Vasko05ba9df2016-01-13 14:40:27 +0100939 switch (reply->type) {
940 case NC_RPL_OK:
Michal Vasko77367452021-02-16 16:32:18 +0100941 if (lyd_new_opaq2(reply_envp, NULL, "ok", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns, NULL)) {
942 lyd_free_tree(reply_envp);
943
944 ERRINT;
945 ret = NC_MSG_ERROR;
946 goto cleanup;
Michal Vasko08611b32016-12-05 13:30:37 +0100947 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100948 break;
949 case NC_RPL_DATA:
Michal Vaskob83a3fa2021-05-26 09:53:42 +0200950 switch (((struct nc_server_reply_data *)reply)->wd) {
Radek Krejci36dfdb32016-09-01 16:56:35 +0200951 case NC_WD_UNKNOWN:
952 case NC_WD_EXPLICIT:
Michal Vasko77367452021-02-16 16:32:18 +0100953 wd = LYD_PRINT_WD_EXPLICIT;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200954 break;
955 case NC_WD_TRIM:
Michal Vasko77367452021-02-16 16:32:18 +0100956 wd = LYD_PRINT_WD_TRIM;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200957 break;
958 case NC_WD_ALL:
Michal Vasko77367452021-02-16 16:32:18 +0100959 wd = LYD_PRINT_WD_ALL;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200960 break;
961 case NC_WD_ALL_TAG:
Michal Vasko77367452021-02-16 16:32:18 +0100962 wd = LYD_PRINT_WD_ALL_TAG;
Radek Krejci36dfdb32016-09-01 16:56:35 +0200963 break;
964 }
Michal Vasko77367452021-02-16 16:32:18 +0100965
966 node = ((struct nc_server_reply_data *)reply)->data;
967 assert(node->schema->nodetype & (LYS_RPC | LYS_ACTION));
Michal Vaskoff7286b2021-07-09 13:13:11 +0200968 LY_LIST_FOR_SAFE(lyd_child(node), next, node) {
Michal Vasko77367452021-02-16 16:32:18 +0100969 /* temporary */
Michal Vaskoff7286b2021-07-09 13:13:11 +0200970 lyd_insert_child(reply_envp, node);
Michal Vasko5a91ce72017-10-19 11:30:02 +0200971 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100972 break;
973 case NC_RPL_ERROR:
Michal Vasko77367452021-02-16 16:32:18 +0100974 /* temporary */
975 lyd_insert_child(reply_envp, ((struct nc_server_reply_error *)reply)->err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100976 break;
977 default:
978 ERRINT;
Radek Krejci047300e2016-03-08 16:46:58 +0100979 nc_write_clb((void *)&arg, NULL, 0, 0);
Michal Vasko131120a2018-05-29 15:44:02 +0200980 ret = NC_MSG_ERROR;
981 goto cleanup;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100982 }
Michal Vasko77367452021-02-16 16:32:18 +0100983
984 /* temporary */
985 ((struct lyd_node_opaq *)reply_envp)->attr = rpc_envp->attr;
986
987 /* print */
988 lyrc = lyd_print_clb(nc_write_xmlclb, (void *)&arg, reply_envp, LYD_XML, LYD_PRINT_SHRINK | wd);
989 ((struct lyd_node_opaq *)reply_envp)->attr = NULL;
990
991 /* cleanup */
992 switch (reply->type) {
993 case NC_RPL_OK:
994 /* just free everything */
995 lyd_free_tree(reply_envp);
996 break;
997 case NC_RPL_DATA:
Michal Vaskoff7286b2021-07-09 13:13:11 +0200998 LY_LIST_FOR_SAFE(lyd_child(reply_envp), next, node) {
Michal Vasko77367452021-02-16 16:32:18 +0100999 /* connect back to the reply structure */
Michal Vaskoff7286b2021-07-09 13:13:11 +02001000 lyd_insert_child(((struct nc_server_reply_data *)reply)->data, node);
Michal Vasko77367452021-02-16 16:32:18 +01001001 }
1002 lyd_free_tree(reply_envp);
1003 break;
1004 case NC_RPL_ERROR:
1005 /* unlink from the data reply */
1006 lyd_unlink_tree(lyd_child(reply_envp));
1007 lyd_free_tree(reply_envp);
1008 break;
1009 default:
1010 break;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +01001011 }
Michal Vasko77367452021-02-16 16:32:18 +01001012
1013 if (lyrc) {
1014 ret = NC_MSG_ERROR;
1015 goto cleanup;
Michal Vasko7f0b0ff2016-11-15 11:02:28 +01001016 }
Radek Krejcife0b3472015-10-12 13:43:42 +02001017 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +01001018
Radek Krejcife0b3472015-10-12 13:43:42 +02001019 case NC_MSG_NOTIF:
Radek Krejci93e80222016-10-03 13:34:25 +02001020 notif = va_arg(ap, struct nc_server_notif *);
1021
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001022 nc_write_clb((void *)&arg, "<notification xmlns=\""NC_NS_NOTIF "\">", 21 + 47 + 2, 0);
Radek Krejci93e80222016-10-03 13:34:25 +02001023 nc_write_clb((void *)&arg, "<eventTime>", 11, 0);
1024 nc_write_clb((void *)&arg, notif->eventtime, strlen(notif->eventtime), 0);
1025 nc_write_clb((void *)&arg, "</eventTime>", 12, 0);
Michal Vasko77367452021-02-16 16:32:18 +01001026 if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, notif->ntf, LYD_XML, LYD_PRINT_SHRINK)) {
Michal Vasko131120a2018-05-29 15:44:02 +02001027 ret = NC_MSG_ERROR;
1028 goto cleanup;
Michal Vasko5a91ce72017-10-19 11:30:02 +02001029 }
mohitarora24878b2962016-11-09 18:45:33 -05001030 nc_write_clb((void *)&arg, "</notification>", 15, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +02001031 break;
Michal Vasko05ba9df2016-01-13 14:40:27 +01001032
Radek Krejcid116db42016-01-08 15:36:30 +01001033 case NC_MSG_HELLO:
1034 if (session->version != NC_VERSION_10) {
Michal Vasko131120a2018-05-29 15:44:02 +02001035 ret = NC_MSG_ERROR;
1036 goto cleanup;
Radek Krejcid116db42016-01-08 15:36:30 +01001037 }
1038 capabilities = va_arg(ap, const char **);
Michal Vaskob83a3fa2021-05-26 09:53:42 +02001039 sid = va_arg(ap, uint32_t *);
Michal Vasko05ba9df2016-01-13 14:40:27 +01001040
Radek Krejcid116db42016-01-08 15:36:30 +01001041 count = asprintf(&buf, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
roman3a95bb22023-10-26 11:07:17 +02001042 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +01001043 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001044 free(buf);
1045 for (i = 0; capabilities[i]; i++) {
Radek Krejci047300e2016-03-08 16:46:58 +01001046 nc_write_clb((void *)&arg, "<capability>", 12, 0);
1047 nc_write_clb((void *)&arg, capabilities[i], strlen(capabilities[i]), 1);
1048 nc_write_clb((void *)&arg, "</capability>", 13, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001049 }
1050 if (sid) {
Michal Vasko16374712024-04-26 14:13:00 +02001051 count = asprintf(&buf, "</capabilities><session-id>%" PRIu32 "</session-id></hello>", *sid);
roman3a95bb22023-10-26 11:07:17 +02001052 NC_CHECK_ERRMEM_GOTO(count == -1, ret = NC_MSG_ERROR, cleanup);
Radek Krejci047300e2016-03-08 16:46:58 +01001053 nc_write_clb((void *)&arg, buf, count, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001054 free(buf);
1055 } else {
Radek Krejci047300e2016-03-08 16:46:58 +01001056 nc_write_clb((void *)&arg, "</capabilities></hello>", 23, 0);
Radek Krejcid116db42016-01-08 15:36:30 +01001057 }
Radek Krejcid116db42016-01-08 15:36:30 +01001058 break;
Michal Vaskoed462342016-01-12 12:33:48 +01001059
Radek Krejcife0b3472015-10-12 13:43:42 +02001060 default:
Michal Vasko131120a2018-05-29 15:44:02 +02001061 ret = NC_MSG_ERROR;
1062 goto cleanup;
Radek Krejcife0b3472015-10-12 13:43:42 +02001063 }
1064
1065 /* flush message */
Radek Krejci047300e2016-03-08 16:46:58 +01001066 nc_write_clb((void *)&arg, NULL, 0, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +02001067
Michal Vasko428087d2016-01-14 16:04:28 +01001068 if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) {
1069 /* error was already written */
Michal Vasko131120a2018-05-29 15:44:02 +02001070 ret = NC_MSG_ERROR;
1071 } else {
1072 /* specific message successfully sent */
1073 ret = type;
Michal Vasko428087d2016-01-14 16:04:28 +01001074 }
1075
Michal Vasko131120a2018-05-29 15:44:02 +02001076cleanup:
1077 va_end(ap);
1078 nc_session_io_unlock(session, __func__);
1079 return ret;
Radek Krejcife0b3472015-10-12 13:43:42 +02001080}
Michal Vasko4eb3c312016-03-01 14:09:37 +01001081
1082void *
1083nc_realloc(void *ptr, size_t size)
1084{
1085 void *ret;
1086
1087 ret = realloc(ptr, size);
1088 if (!ret) {
1089 free(ptr);
1090 }
1091
1092 return ret;
1093}
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001094
1095struct passwd *
romanf6e32012023-04-24 15:51:26 +02001096nc_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 +02001097{
1098 struct passwd *pwd = NULL;
Michal Vasko7e06ee52021-11-02 08:53:05 +01001099 long sys_size;
1100 int ret;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001101
1102 do {
Michal Vasko7e06ee52021-11-02 08:53:05 +01001103 if (!*buf_size) {
1104 /* learn suitable buffer size */
1105 sys_size = sysconf(_SC_GETPW_R_SIZE_MAX);
1106 *buf_size = (sys_size == -1) ? 2048 : sys_size;
1107 } else {
1108 /* enlarge buffer */
1109 *buf_size += 2048;
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001110 }
1111
Michal Vasko7e06ee52021-11-02 08:53:05 +01001112 /* allocate some buffer */
1113 *buf = nc_realloc(*buf, *buf_size);
roman3a95bb22023-10-26 11:07:17 +02001114 NC_CHECK_ERRMEM_RET(!*buf, NULL);
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001115
romanf6e32012023-04-24 15:51:26 +02001116 if (username) {
1117 ret = getpwnam_r(username, pwd_buf, *buf, *buf_size, &pwd);
1118 } else {
1119 ret = getpwuid_r(uid, pwd_buf, *buf, *buf_size, &pwd);
1120 }
Michal Vasko7e06ee52021-11-02 08:53:05 +01001121 } while (ret && (ret == ERANGE));
1122
1123 if (ret) {
romanf6e32012023-04-24 15:51:26 +02001124 if (username) {
1125 ERR(NULL, "Retrieving username \"%s\" passwd entry failed (%s).", username, strerror(ret));
1126 } else {
1127 ERR(NULL, "Retrieving UID \"%lu\" passwd entry failed (%s).", (unsigned long)uid, strerror(ret));
1128 }
Michal Vasko7e06ee52021-11-02 08:53:05 +01001129 }
Jan Kundrát6aa0eeb2021-10-08 21:10:05 +02001130 return pwd;
1131}