blob: 2f86c1fe03f287390b2042b95aa9c7d6de4b2318 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
2 * \file session_server.c
3 * \author Michal Vasko <mvasko@cesnet.cz>
4 * \brief libnetconf2 server session manipulation functions
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of the Company nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 */
22
23#include <stdint.h>
24#include <stdlib.h>
25#include <errno.h>
26#include <string.h>
27#include <poll.h>
28#include <sys/types.h>
29#include <sys/socket.h>
30#include <netinet/in.h>
31#include <arpa/inet.h>
32#include <unistd.h>
Michal Vaskob48aa812016-01-18 14:13:09 +010033#include <pthread.h>
Michal Vasko086311b2016-01-08 09:53:11 +010034
Michal Vasko1a38c862016-01-15 15:50:07 +010035#include "libnetconf.h"
Michal Vasko086311b2016-01-08 09:53:11 +010036#include "session_server.h"
37
Michal Vaskob48aa812016-01-18 14:13:09 +010038struct nc_server_opts server_opts = {
39 .bind_lock = PTHREAD_MUTEX_INITIALIZER
40};
Michal Vasko086311b2016-01-08 09:53:11 +010041
Michal Vasko1a38c862016-01-15 15:50:07 +010042API void
43nc_session_set_term_reason(struct nc_session *session, NC_SESSION_TERM_REASON reason)
44{
45 if (!session || !reason) {
46 ERRARG;
47 return;
48 }
49
50 session->term_reason = reason;
51}
52
Michal Vasko086311b2016-01-08 09:53:11 +010053int
54nc_sock_listen(const char *address, uint32_t port)
55{
56 const int optVal = 1;
57 const socklen_t optLen = sizeof(optVal);
58 int is_ipv4, sock;
59 struct sockaddr_storage saddr;
60
61 struct sockaddr_in *saddr4;
62 struct sockaddr_in6 *saddr6;
63
64
65 if (!strchr(address, ':')) {
66 is_ipv4 = 1;
67 } else {
68 is_ipv4 = 0;
69 }
70
71 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
72 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +010073 ERR("Failed to create socket (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +010074 goto fail;
75 }
76
77 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&optVal, optLen)) {
Michal Vaskod083db62016-01-19 10:31:29 +010078 ERR("Could not set socket SO_REUSEADDR socket option (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +010079 goto fail;
80 }
81
82 bzero(&saddr, sizeof(struct sockaddr_storage));
83 if (is_ipv4) {
84 saddr4 = (struct sockaddr_in *)&saddr;
85
86 saddr4->sin_family = AF_INET;
87 saddr4->sin_port = htons(port);
88
89 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
Michal Vaskod083db62016-01-19 10:31:29 +010090 ERR("Failed to convert IPv4 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +010091 goto fail;
92 }
93
94 if (bind(sock, (struct sockaddr *)saddr4, sizeof(struct sockaddr_in)) == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +010095 ERR("Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +010096 goto fail;
97 }
98
99 } else {
100 saddr6 = (struct sockaddr_in6 *)&saddr;
101
102 saddr6->sin6_family = AF_INET6;
103 saddr6->sin6_port = htons(port);
104
105 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100106 ERR("Failed to convert IPv6 address \"%s\".", address);
Michal Vasko086311b2016-01-08 09:53:11 +0100107 goto fail;
108 }
109
110 if (bind(sock, (struct sockaddr *)saddr6, sizeof(struct sockaddr_in6)) == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100111 ERR("Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100112 goto fail;
113 }
114 }
115
Michal Vaskofb89d772016-01-08 12:25:35 +0100116 if (listen(sock, NC_REVERSE_QUEUE) == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100117 ERR("Unable to start listening on \"%s\" port %d (%s).", address, port, strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100118 goto fail;
119 }
120
121 return sock;
122
123fail:
124 if (sock > -1) {
125 close(sock);
126 }
127
128 return -1;
129}
130
131int
Michal Vasko9e036d52016-01-08 10:49:26 +0100132nc_sock_accept(struct nc_bind *binds, uint16_t bind_count, int timeout, NC_TRANSPORT_IMPL *ti, char **host, uint16_t *port)
Michal Vasko086311b2016-01-08 09:53:11 +0100133{
134 uint16_t i;
135 struct pollfd *pfd;
136 struct sockaddr_storage saddr;
137 socklen_t saddr_len = sizeof(saddr);
138 int ret, sock = -1;
139
140 pfd = malloc(bind_count * sizeof *pfd);
141 for (i = 0; i < bind_count; ++i) {
142 pfd[i].fd = binds[i].sock;
143 pfd[i].events = POLLIN;
144 pfd[i].revents = 0;
145 }
146
147 /* poll for a new connection */
148 errno = 0;
149 ret = poll(pfd, bind_count, timeout);
150 if (!ret) {
151 /* we timeouted */
152 free(pfd);
153 return 0;
154 } else if (ret == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100155 ERR("Poll failed (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100156 free(pfd);
157 return -1;
158 }
159
160 for (i = 0; i < bind_count; ++i) {
161 if (pfd[i].revents & POLLIN) {
162 sock = pfd[i].fd;
163 break;
164 }
165 }
166 free(pfd);
167
168 if (sock == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100169 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100170 return -1;
171 }
172
173 ret = accept(sock, (struct sockaddr *)&saddr, &saddr_len);
174 if (ret == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100175 ERR("Accept failed (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100176 return -1;
177 }
178
Michal Vasko9e036d52016-01-08 10:49:26 +0100179 if (ti) {
180 *ti = binds[i].ti;
181 }
182
Michal Vasko086311b2016-01-08 09:53:11 +0100183 /* host was requested */
184 if (host) {
185 if (saddr.ss_family == AF_INET) {
186 *host = malloc(15);
187 if (!inet_ntop(AF_INET, &((struct sockaddr_in *)&saddr)->sin_addr.s_addr, *host, 15)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100188 ERR("inet_ntop failed (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100189 free(*host);
190 *host = NULL;
191 }
192
193 if (port) {
194 *port = ntohs(((struct sockaddr_in *)&saddr)->sin_port);
195 }
196 } else if (saddr.ss_family == AF_INET6) {
197 *host = malloc(40);
198 if (!inet_ntop(AF_INET6, ((struct sockaddr_in6 *)&saddr)->sin6_addr.s6_addr, *host, 40)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100199 ERR("inet_ntop failed (%s).", strerror(errno));
Michal Vasko086311b2016-01-08 09:53:11 +0100200 free(*host);
201 *host = NULL;
202 }
203
204 if (port) {
205 *port = ntohs(((struct sockaddr_in6 *)&saddr)->sin6_port);
206 }
207 } else {
Michal Vaskod083db62016-01-19 10:31:29 +0100208 ERR("Source host of an unknown protocol family.");
Michal Vasko086311b2016-01-08 09:53:11 +0100209 }
210 }
211
212 return ret;
213}
214
Michal Vasko05ba9df2016-01-13 14:40:27 +0100215static struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100216nc_clb_default_get_schema(struct lyd_node *rpc, struct nc_session *UNUSED(session))
Michal Vasko05ba9df2016-01-13 14:40:27 +0100217{
218 const char *identifier = NULL, *version = NULL, *format = NULL;
219 char *model_data = NULL;
220 const struct lys_module *module;
221 struct nc_server_error *err;
222 struct lyd_node *child, *data = NULL;
223 const struct lys_node *sdata;
224
225 LY_TREE_FOR(rpc->child, child) {
226 if (!strcmp(child->schema->name, "identifier")) {
227 identifier = ((struct lyd_node_leaf_list *)child)->value_str;
228 } else if (!strcmp(child->schema->name, "version")) {
229 version = ((struct lyd_node_leaf_list *)child)->value_str;
230 } else if (!strcmp(child->schema->name, "format")) {
231 format = ((struct lyd_node_leaf_list *)child)->value_str;
232 }
233 }
234
235 /* check version */
236 if (version && (strlen(version) != 10) && strcmp(version, "1.0")) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100237 err = nc_err(NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
238 nc_err_set_msg(err, "The requested version is not supported.", "en");
239 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100240 }
241
242 /* check and get module with the name identifier */
243 module = ly_ctx_get_module(server_opts.ctx, identifier, version);
244 if (!module) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100245 err = nc_err(NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
246 nc_err_set_msg(err, "The requested schema was not found.", "en");
247 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100248 }
249
250 /* check format */
251 if (!format || !strcmp(format, "yang")) {
252 lys_print_mem(&model_data, module, LYS_OUT_YANG, NULL);
253 } else if (!strcmp(format, "yin")) {
254 lys_print_mem(&model_data, module, LYS_OUT_YIN, NULL);
255 } else {
Michal Vasko1a38c862016-01-15 15:50:07 +0100256 err = nc_err(NC_ERR_INVALID_VALUE, NC_ERR_TYPE_APP);
257 nc_err_set_msg(err, "The requested format is not supported.", "en");
258 return nc_server_reply_err(err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100259 }
260
Michal Vasko0473c4c2016-01-19 10:40:06 +0100261 module = ly_ctx_get_module(server_opts.ctx, "ietf-netconf-monitoring", NULL);
262 if (module) {
263 sdata = lys_get_node(module, "/get-schema/output/data");
264 }
265 if (model_data && sdata) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100266 data = lyd_output_new_anyxml(sdata, model_data);
267 }
268 free(model_data);
269 if (!data) {
270 ERRINT;
271 return NULL;
272 }
273
274 return nc_server_reply_data(data, NC_PARAMTYPE_FREE);
275}
276
277static struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100278nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100279{
Michal Vasko428087d2016-01-14 16:04:28 +0100280 session->term_reason = NC_SESSION_TERM_CLOSED;
281 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100282}
283
Michal Vasko086311b2016-01-08 09:53:11 +0100284API int
285nc_server_init(struct ly_ctx *ctx)
286{
Michal Vasko05ba9df2016-01-13 14:40:27 +0100287 const struct lys_node *rpc;
Michal Vasko0473c4c2016-01-19 10:40:06 +0100288 const struct lys_module *mod;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100289
Michal Vasko086311b2016-01-08 09:53:11 +0100290 if (!ctx) {
291 ERRARG;
292 return -1;
293 }
294
Michal Vasko05ba9df2016-01-13 14:40:27 +0100295 /* set default <get-schema> callback if not specified */
Michal Vasko0473c4c2016-01-19 10:40:06 +0100296 rpc = NULL;
297 mod = ly_ctx_get_module(ctx, "ietf-netconf-monitoring", NULL);
298 if (mod) {
299 rpc = lys_get_node(mod, "/get-schema");
300 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100301 if (rpc && !rpc->private) {
302 lys_set_private(rpc, nc_clb_default_get_schema);
303 }
304
305 /* set default <close-session> callback if not specififed */
Michal Vasko0473c4c2016-01-19 10:40:06 +0100306 rpc = NULL;
307 mod = ly_ctx_get_module(ctx, "ietf-netconf", NULL);
308 if (mod) {
309 rpc = lys_get_node(mod, "/close-session");
310 }
Michal Vasko05ba9df2016-01-13 14:40:27 +0100311 if (rpc && !rpc->private) {
312 lys_set_private(rpc, nc_clb_default_close_session);
313 }
314
Michal Vasko086311b2016-01-08 09:53:11 +0100315 server_opts.ctx = ctx;
Michal Vaskob48aa812016-01-18 14:13:09 +0100316
317 server_opts.new_session_id = 1;
318 pthread_spin_init(&server_opts.sid_lock, PTHREAD_PROCESS_PRIVATE);
319
Michal Vasko086311b2016-01-08 09:53:11 +0100320 return 0;
321}
322
Michal Vaskob48aa812016-01-18 14:13:09 +0100323API void
324nc_server_destroy(void)
325{
326 pthread_spin_destroy(&server_opts.sid_lock);
327
328#if defined(ENABLE_SSH) || defined(ENABLE_TLS)
329 nc_server_del_bind(NULL, 0, 0);
330#endif
331}
332
Michal Vasko086311b2016-01-08 09:53:11 +0100333API int
334nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
335{
336 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)
337 || (also_supported && !(also_supported & (NC_WD_ALL | NC_WD_ALL_TAG | NC_WD_TRIM | NC_WD_EXPLICIT)))) {
338 ERRARG;
339 return -1;
340 }
341
342 server_opts.wd_basic_mode = basic_mode;
343 server_opts.wd_also_supported = also_supported;
344 return 0;
345}
346
Michal Vasko1a38c862016-01-15 15:50:07 +0100347API void
Michal Vasko086311b2016-01-08 09:53:11 +0100348nc_server_set_capab_interleave(int interleave_support)
349{
350 if (interleave_support) {
351 server_opts.interleave_capab = 1;
352 } else {
353 server_opts.interleave_capab = 0;
354 }
Michal Vasko086311b2016-01-08 09:53:11 +0100355}
356
Michal Vasko1a38c862016-01-15 15:50:07 +0100357API void
Michal Vasko086311b2016-01-08 09:53:11 +0100358nc_server_set_hello_timeout(uint16_t hello_timeout)
359{
Michal Vasko086311b2016-01-08 09:53:11 +0100360 server_opts.hello_timeout = hello_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100361}
362
Michal Vasko1a38c862016-01-15 15:50:07 +0100363API void
Michal Vasko086311b2016-01-08 09:53:11 +0100364nc_server_set_idle_timeout(uint16_t idle_timeout)
365{
Michal Vasko086311b2016-01-08 09:53:11 +0100366 server_opts.idle_timeout = idle_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100367}
368
369API int
Michal Vasko1a38c862016-01-15 15:50:07 +0100370nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +0100371{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100372 if (!server_opts.ctx || (fdin < 0) || (fdout < 0) || !username || !session) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100373 ERRARG;
374 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100375 }
376
377 /* prepare session structure */
Michal Vasko1a38c862016-01-15 15:50:07 +0100378 *session = calloc(1, sizeof **session);
379 if (!(*session)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100380 ERRMEM;
Michal Vasko1a38c862016-01-15 15:50:07 +0100381 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100382 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100383 (*session)->status = NC_STATUS_STARTING;
384 (*session)->side = NC_SERVER;
Michal Vasko086311b2016-01-08 09:53:11 +0100385
386 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100387 (*session)->ti_type = NC_TI_FD;
388 (*session)->ti.fd.in = fdin;
389 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100390
391 /* assign context (dicionary needed for handshake) */
Michal Vasko1a38c862016-01-15 15:50:07 +0100392 (*session)->flags = NC_SESSION_SHAREDCTX;
393 (*session)->ctx = server_opts.ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100394
Michal Vaskob48aa812016-01-18 14:13:09 +0100395 /* assign new SID atomically */
396 pthread_spin_lock(&server_opts.sid_lock);
397 (*session)->id = server_opts.new_session_id++;
398 pthread_spin_unlock(&server_opts.sid_lock);
399
Michal Vasko086311b2016-01-08 09:53:11 +0100400 /* NETCONF handshake */
Michal Vasko1a38c862016-01-15 15:50:07 +0100401 if (nc_handshake(*session)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100402 goto fail;
403 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100404 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100405
Michal Vasko1a38c862016-01-15 15:50:07 +0100406 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100407
408fail:
Michal Vasko1a38c862016-01-15 15:50:07 +0100409 nc_session_free(*session);
410 *session = NULL;
411 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100412}
Michal Vasko9e036d52016-01-08 10:49:26 +0100413
Michal Vasko428087d2016-01-14 16:04:28 +0100414API struct nc_pollsession *
415nc_ps_new(void)
416{
417 return calloc(1, sizeof(struct nc_pollsession));
418}
419
420API void
421nc_ps_free(struct nc_pollsession *ps)
422{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100423 if (!ps) {
424 return;
425 }
426
Michal Vasko428087d2016-01-14 16:04:28 +0100427 free(ps->sessions);
428 free(ps);
429}
430
431API int
432nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
433{
434 if (!ps || !session) {
435 ERRARG;
436 return -1;
437 }
438
439 ++ps->session_count;
440 ps->sessions = realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
441
442 switch (session->ti_type) {
443 case NC_TI_FD:
444 ps->sessions[ps->session_count - 1].fd = session->ti.fd.in;
445 break;
446
447#ifdef ENABLE_SSH
448 case NC_TI_LIBSSH:
449 ps->sessions[ps->session_count - 1].fd = ssh_get_fd(session->ti.libssh.session);
450 break;
451#endif
452
453#ifdef ENABLE_TLS
454 case NC_TI_OPENSSL:
455 ps->sessions[ps->session_count - 1].fd = SSL_get_rfd(session->ti.tls);
456 break;
457#endif
458
459 default:
460 ERRINT;
461 return -1;
462 }
463 ps->sessions[ps->session_count - 1].events = POLLIN;
464 ps->sessions[ps->session_count - 1].revents = 0;
465 ps->sessions[ps->session_count - 1].session = session;
466
467 return 0;
468}
469
470API int
471nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
472{
473 uint16_t i;
474
475 if (!ps || !session) {
476 ERRARG;
477 return -1;
478 }
479
480 for (i = 0; i < ps->session_count; ++i) {
481 if (ps->sessions[i].session == session) {
482 --ps->session_count;
483 memmove(&ps->sessions[i], &ps->sessions[i + 1], ps->session_count - i);
484 return 0;
485 }
486 }
487
488 return 1;
489}
490
491/* must be called holding the session lock! */
492static NC_MSG_TYPE
493nc_recv_rpc(struct nc_session *session, struct nc_server_rpc **rpc)
494{
495 struct lyxml_elem *xml = NULL;
496 NC_MSG_TYPE msgtype;
497
498 if (!session || !rpc) {
499 ERRARG;
500 return NC_MSG_ERROR;
501 } else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100502 ERR("Session %u: invalid session to receive RPCs.", session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100503 return NC_MSG_ERROR;
504 }
505
506 msgtype = nc_read_msg(session, &xml);
507
508 switch (msgtype) {
509 case NC_MSG_RPC:
510 *rpc = malloc(sizeof **rpc);
511 (*rpc)->tree = lyd_parse_xml(server_opts.ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_RPC);
512 (*rpc)->root = xml;
513 break;
514 case NC_MSG_HELLO:
Michal Vaskod083db62016-01-19 10:31:29 +0100515 ERR("Session %u: received another <hello> message.", session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100516 goto error;
517 case NC_MSG_REPLY:
Michal Vaskod083db62016-01-19 10:31:29 +0100518 ERR("Session %u: received <rpc-reply> from NETCONF client.", session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100519 goto error;
520 case NC_MSG_NOTIF:
Michal Vaskod083db62016-01-19 10:31:29 +0100521 ERR("Session %u: received <notification> from NETCONF client.", session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100522 goto error;
523 default:
524 /* NC_MSG_ERROR - pass it out;
525 * NC_MSG_WOULDBLOCK and NC_MSG_NONE is not returned by nc_read_msg()
526 */
527 break;
528 }
529
530 return msgtype;
531
532error:
533 /* cleanup */
534 lyxml_free(server_opts.ctx, xml);
535
536 return NC_MSG_ERROR;
537}
538
539/* must be called holding the session lock! */
540static NC_MSG_TYPE
541nc_send_reply(struct nc_session *session, struct nc_server_rpc *rpc)
542{
543 nc_rpc_clb clb;
544 struct nc_server_reply *reply;
545 int ret;
546
547 /* no callback, reply with a not-implemented error */
548 if (!rpc->tree->schema->private) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100549 reply = nc_server_reply_err(nc_err(NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_PROT));
Michal Vasko428087d2016-01-14 16:04:28 +0100550 } else {
551 clb = (nc_rpc_clb)rpc->tree->schema->private;
552 reply = clb(rpc->tree, session);
553 }
554
555 if (!reply) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100556 reply = nc_server_reply_err(nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +0100557 }
558
559 ret = nc_write_msg(session, NC_MSG_REPLY, rpc->root, reply);
560
561 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
562 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
563 session->status = NC_STATUS_INVALID;
564 }
565
566 if (ret == -1) {
Michal Vaskod083db62016-01-19 10:31:29 +0100567 ERR("Session %u: failed to write reply.", session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100568 nc_server_reply_free(reply);
569 return NC_MSG_ERROR;
570 }
571 nc_server_reply_free(reply);
572
573 return NC_MSG_REPLY;
574}
575
576API int
577nc_ps_poll(struct nc_pollsession *ps, int timeout)
578{
579 int ret;
580 uint16_t i;
581 NC_MSG_TYPE msgtype;
582 struct nc_session *session;
583 struct nc_server_rpc *rpc;
584 struct timespec old_ts, new_ts;
585
586 if (!ps || !ps->session_count) {
587 ERRARG;
588 return -1;
589 }
590
591 for (i = 0; i < ps->session_count; ++i) {
592 if (ps->sessions[i].session->status != NC_STATUS_RUNNING) {
Michal Vaskod083db62016-01-19 10:31:29 +0100593 ERR("Session %u: session not running.", ps->sessions[i].session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100594 return -1;
595 }
596 }
597
598 if (timeout > 0) {
599 clock_gettime(CLOCK_MONOTONIC_RAW, &old_ts);
600 }
601
602retry_poll:
603 ret = poll((struct pollfd *)ps->sessions, ps->session_count, timeout);
604 if (ret < 1) {
605 return ret;
606 }
607
608 /* find the first fd with POLLIN, we don't care if there are more */
609 for (i = 0; i < ps->session_count; ++i) {
610 if (ps->sessions[i].revents & POLLIN) {
611#ifdef ENABLE_SSH
612 if (ps->sessions[i].session->ti_type == NC_TI_LIBSSH) {
613 /* things are not that simple with SSH, we need to check the channel */
614 ret = ssh_channel_poll_timeout(ps->sessions[i].session->ti.libssh.channel, 0, 0);
615 /* not this one */
616 if (!ret) {
617 if (i == ps->session_count - 1) {
618 /* last session and it is not the right channel, ... */
619 if (timeout > 0) {
620 /* ... decrease timeout, wait it all out and try again, last time */
621 clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
622
623 timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
624 timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
625 if (timeout < 0) {
626 ERRINT;
627 return -1;
628 }
629
630 old_ts = new_ts;
631 } else if (!timeout) {
632 /* ... timeout is 0, so that is it */
633 return 0;
634 } else {
635 /* ... retry polling reasonable time apart */
636 usleep(NC_TIMEOUT_STEP);
637 goto retry_poll;
638 }
639 }
640 /* check other sessions */
641 continue;
642 } else if (ret == SSH_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100643 ERR("Session %u: SSH channel error (%s).", ps->sessions[i].session->id,
Michal Vasko428087d2016-01-14 16:04:28 +0100644 ssh_get_error(ps->sessions[i].session->ti.libssh.session));
645 ps->sessions[i].session->status = NC_STATUS_INVALID;
646 ps->sessions[i].session->term_reason = NC_SESSION_TERM_OTHER;
647 return 2;
648 } else if (ret == SSH_EOF) {
Michal Vaskod083db62016-01-19 10:31:29 +0100649 ERR("Session %u: communication channel unexpectedly closed (libssh).",
650 ps->sessions[i].session->id);
Michal Vasko428087d2016-01-14 16:04:28 +0100651 ps->sessions[i].session->status = NC_STATUS_INVALID;
652 ps->sessions[i].session->term_reason = NC_SESSION_TERM_DROPPED;
653 return 2;
654 }
655 }
656#endif /* ENABLE_SSH */
657
658 break;
659 }
660 }
661
662 if (i == ps->session_count) {
663 ERRINT;
664 return -1;
665 }
666
667 /* this is the session with some data available for reading */
668 session = ps->sessions[i].session;
669
670 if (timeout > 0) {
671 clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
672
673 /* subtract elapsed time */
674 timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
675 timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
676 if (timeout < 0) {
677 ERRINT;
678 return -1;
679 }
680 }
681
682 /* reading an RPC and sending a reply must be atomic */
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100683 ret = nc_timedlock(session->ti_lock, timeout, NULL);
684 if (ret != 1) {
685 /* error or timeout */
686 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +0100687 }
688
689 msgtype = nc_recv_rpc(session, &rpc);
690 if (msgtype == NC_MSG_ERROR) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100691 pthread_mutex_unlock(session->ti_lock);
Michal Vasko428087d2016-01-14 16:04:28 +0100692 if (session->status != NC_STATUS_RUNNING) {
693 return 2;
694 }
695 return -1;
696 }
697
698 /* process RPC */
699 msgtype = nc_send_reply(session, rpc);
700
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100701 pthread_mutex_unlock(session->ti_lock);
Michal Vasko428087d2016-01-14 16:04:28 +0100702
703 if (msgtype == NC_MSG_ERROR) {
704 nc_server_rpc_free(rpc);
705 if (session->status != NC_STATUS_RUNNING) {
706 return 2;
707 }
708 return -1;
709 }
710
711 nc_server_rpc_free(rpc);
712 return 1;
713}
714
Michal Vasko9e036d52016-01-08 10:49:26 +0100715#if defined(ENABLE_SSH) || defined(ENABLE_TLS)
716
717API int
718nc_server_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
719{
720 int sock;
721
722 if (!address || !port || ((ti != NC_TI_LIBSSH) && (ti != NC_TI_OPENSSL))) {
723 ERRARG;
724 return -1;
725 }
726
727 sock = nc_sock_listen(address, port);
728 if (sock == -1) {
729 return -1;
730 }
731
Michal Vaskob48aa812016-01-18 14:13:09 +0100732 /* LOCK */
733 pthread_mutex_lock(&server_opts.bind_lock);
734
Michal Vasko9e036d52016-01-08 10:49:26 +0100735 ++server_opts.bind_count;
736 server_opts.binds = realloc(server_opts.binds, server_opts.bind_count * sizeof *server_opts.binds);
737
738 server_opts.binds[server_opts.bind_count - 1].address = strdup(address);
739 server_opts.binds[server_opts.bind_count - 1].port = port;
740 server_opts.binds[server_opts.bind_count - 1].sock = sock;
741 server_opts.binds[server_opts.bind_count - 1].ti = ti;
742
Michal Vaskob48aa812016-01-18 14:13:09 +0100743 /* UNLOCK */
744 pthread_mutex_unlock(&server_opts.bind_lock);
745
Michal Vasko9e036d52016-01-08 10:49:26 +0100746 return 0;
747}
748
749API int
750nc_server_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
751{
752 uint32_t i;
753 int ret = -1;
754
Michal Vaskob48aa812016-01-18 14:13:09 +0100755 /* LOCK */
756 pthread_mutex_lock(&server_opts.bind_lock);
757
Michal Vasko1a38c862016-01-15 15:50:07 +0100758 if (!address && !port && !ti) {
759 for (i = 0; i < server_opts.bind_count; ++i) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100760 close(server_opts.binds[i].sock);
761 free(server_opts.binds[i].address);
762
Michal Vasko9e036d52016-01-08 10:49:26 +0100763 ret = 0;
764 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100765 free(server_opts.binds);
766 server_opts.binds = NULL;
767 server_opts.bind_count = 0;
768 } else {
769 for (i = 0; i < server_opts.bind_count; ++i) {
770 if ((!address || !strcmp(server_opts.binds[i].address, address))
771 && (!port || (server_opts.binds[i].port == port))
772 && (!ti || (server_opts.binds[i].ti == ti))) {
773 close(server_opts.binds[i].sock);
774 free(server_opts.binds[i].address);
775
776 --server_opts.bind_count;
777 memmove(&server_opts.binds[i], &server_opts.binds[i + 1], (server_opts.bind_count - i) * sizeof *server_opts.binds);
778
779 ret = 0;
780 }
781 }
Michal Vasko9e036d52016-01-08 10:49:26 +0100782 }
783
Michal Vaskob48aa812016-01-18 14:13:09 +0100784 /* UNLOCK */
785 pthread_mutex_unlock(&server_opts.bind_lock);
786
Michal Vasko9e036d52016-01-08 10:49:26 +0100787 return ret;
788}
789
Michal Vasko1a38c862016-01-15 15:50:07 +0100790API int
791nc_accept(int timeout, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +0100792{
793 NC_TRANSPORT_IMPL ti;
Michal Vasko1a38c862016-01-15 15:50:07 +0100794 int sock, ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100795 char *host;
796 uint16_t port;
Michal Vasko9e036d52016-01-08 10:49:26 +0100797
Michal Vasko1a38c862016-01-15 15:50:07 +0100798 if (!server_opts.ctx || !server_opts.binds || !session) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100799 ERRARG;
Michal Vasko1a38c862016-01-15 15:50:07 +0100800 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100801 }
802
Michal Vaskob48aa812016-01-18 14:13:09 +0100803 /* LOCK */
804 pthread_mutex_lock(&server_opts.bind_lock);
805
806 ret = nc_sock_accept(server_opts.binds, server_opts.bind_count, timeout, &ti, &host, &port);
807
808 /* UNLOCK */
809 pthread_mutex_unlock(&server_opts.bind_lock);
810
811 if (ret < 1) {
812 return ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100813 }
Michal Vaskob48aa812016-01-18 14:13:09 +0100814 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100815
Michal Vasko1a38c862016-01-15 15:50:07 +0100816 *session = calloc(1, sizeof **session);
Michal Vasko9e036d52016-01-08 10:49:26 +0100817 if (!session) {
818 ERRMEM;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100819 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100820 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100821 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100822 (*session)->status = NC_STATUS_STARTING;
823 (*session)->side = NC_SERVER;
824 (*session)->ctx = server_opts.ctx;
825 (*session)->flags = NC_SESSION_SHAREDCTX;
826 (*session)->host = lydict_insert_zc(server_opts.ctx, host);
827 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +0100828
829 /* transport lock */
Michal Vasko1a38c862016-01-15 15:50:07 +0100830 (*session)->ti_lock = malloc(sizeof *(*session)->ti_lock);
831 if (!(*session)->ti_lock) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100832 ERRMEM;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100833 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100834 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100835 goto fail;
836 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100837 pthread_mutex_init((*session)->ti_lock, NULL);
Michal Vasko9e036d52016-01-08 10:49:26 +0100838
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100839 /* sock gets assigned to session or closed */
Michal Vasko9e036d52016-01-08 10:49:26 +0100840 if (ti == NC_TI_LIBSSH) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100841 ret = nc_accept_ssh_session(*session, sock, timeout);
842 if (ret < 1) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100843 goto fail;
844 }
845 } else if (ti == NC_TI_OPENSSL) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100846 ret = nc_accept_tls_session(*session, sock, timeout);
847 if (ret < 1) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100848 goto fail;
849 }
850 } else {
851 ERRINT;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100852 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100853 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100854 goto fail;
855 }
856
Michal Vaskob48aa812016-01-18 14:13:09 +0100857 /* assign new SID atomically */
858 /* LOCK */
859 pthread_spin_lock(&server_opts.sid_lock);
860 (*session)->id = server_opts.new_session_id++;
861 /* UNLOCK */
862 pthread_spin_unlock(&server_opts.sid_lock);
863
Michal Vasko9e036d52016-01-08 10:49:26 +0100864 /* NETCONF handshake */
Michal Vasko1a38c862016-01-15 15:50:07 +0100865 if (nc_handshake(*session)) {
866 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100867 goto fail;
868 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100869 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +0100870
Michal Vasko1a38c862016-01-15 15:50:07 +0100871 return 1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100872
873fail:
Michal Vasko1a38c862016-01-15 15:50:07 +0100874 nc_session_free(*session);
875 *session = NULL;
876 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100877}
878
879#endif /* ENABLE_SSH || ENABLE_TLS */