blob: 51b9a998498352d3fa028b3267f830fc12d29b9c [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 Vaskob48aa812016-01-18 14:13:09 +010073 ERR("%s: could not create socket (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +010078 ERR("%s: could not set socket SO_REUSEADDR option (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +010090 ERR("%s: failed to convert IPv4 address \"%s\".", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +010095 ERR("%s: could not bind \"%s\" port %d (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100106 ERR("%s: failed to convert IPv6 address \"%s\".", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100111 ERR("%s: could not bind \"%s\" port %d (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100117 ERR("%s: unable to start listening on \"%s\" port %d (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100155 ERR("%s: poll failed (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100169 ERR("%s: fatal error (%s:%d).", __func__, __FILE__, __LINE__);
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 Vaskob48aa812016-01-18 14:13:09 +0100175 ERR("%s: accept failed (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100188 ERR("%s: inet_ntop failed (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100199 ERR("%s: inet_ntop failed (%s).", __func__, 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 Vaskob48aa812016-01-18 14:13:09 +0100208 ERR("%s: source host of an unknown protocol family.", __func__);
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
261 sdata = ly_ctx_get_node(server_opts.ctx, "/ietf-netconf-monitoring:get-schema/output/data");
262 if (model_data) {
263 data = lyd_output_new_anyxml(sdata, model_data);
264 }
265 free(model_data);
266 if (!data) {
267 ERRINT;
268 return NULL;
269 }
270
271 return nc_server_reply_data(data, NC_PARAMTYPE_FREE);
272}
273
274static struct nc_server_reply *
Michal Vasko428087d2016-01-14 16:04:28 +0100275nc_clb_default_close_session(struct lyd_node *UNUSED(rpc), struct nc_session *session)
Michal Vasko05ba9df2016-01-13 14:40:27 +0100276{
Michal Vasko428087d2016-01-14 16:04:28 +0100277 session->term_reason = NC_SESSION_TERM_CLOSED;
278 return nc_server_reply_ok();
Michal Vasko05ba9df2016-01-13 14:40:27 +0100279}
280
Michal Vasko086311b2016-01-08 09:53:11 +0100281API int
282nc_server_init(struct ly_ctx *ctx)
283{
Michal Vasko05ba9df2016-01-13 14:40:27 +0100284 const struct lys_node *rpc;
285
Michal Vasko086311b2016-01-08 09:53:11 +0100286 if (!ctx) {
287 ERRARG;
288 return -1;
289 }
290
Michal Vasko05ba9df2016-01-13 14:40:27 +0100291 /* set default <get-schema> callback if not specified */
292 rpc = ly_ctx_get_node(ctx, "/ietf-netconf-monitoring:get-schema");
293 if (rpc && !rpc->private) {
294 lys_set_private(rpc, nc_clb_default_get_schema);
295 }
296
297 /* set default <close-session> callback if not specififed */
298 rpc = ly_ctx_get_node(ctx, "/ietf-netconf:close-session");
299 if (rpc && !rpc->private) {
300 lys_set_private(rpc, nc_clb_default_close_session);
301 }
302
Michal Vasko086311b2016-01-08 09:53:11 +0100303 server_opts.ctx = ctx;
Michal Vaskob48aa812016-01-18 14:13:09 +0100304
305 server_opts.new_session_id = 1;
306 pthread_spin_init(&server_opts.sid_lock, PTHREAD_PROCESS_PRIVATE);
307
Michal Vasko086311b2016-01-08 09:53:11 +0100308 return 0;
309}
310
Michal Vaskob48aa812016-01-18 14:13:09 +0100311API void
312nc_server_destroy(void)
313{
314 pthread_spin_destroy(&server_opts.sid_lock);
315
316#if defined(ENABLE_SSH) || defined(ENABLE_TLS)
317 nc_server_del_bind(NULL, 0, 0);
318#endif
319}
320
Michal Vasko086311b2016-01-08 09:53:11 +0100321API int
322nc_server_set_capab_withdefaults(NC_WD_MODE basic_mode, int also_supported)
323{
324 if (!basic_mode || (basic_mode == NC_WD_ALL_TAG)
325 || (also_supported && !(also_supported & (NC_WD_ALL | NC_WD_ALL_TAG | NC_WD_TRIM | NC_WD_EXPLICIT)))) {
326 ERRARG;
327 return -1;
328 }
329
330 server_opts.wd_basic_mode = basic_mode;
331 server_opts.wd_also_supported = also_supported;
332 return 0;
333}
334
Michal Vasko1a38c862016-01-15 15:50:07 +0100335API void
Michal Vasko086311b2016-01-08 09:53:11 +0100336nc_server_set_capab_interleave(int interleave_support)
337{
338 if (interleave_support) {
339 server_opts.interleave_capab = 1;
340 } else {
341 server_opts.interleave_capab = 0;
342 }
Michal Vasko086311b2016-01-08 09:53:11 +0100343}
344
Michal Vasko1a38c862016-01-15 15:50:07 +0100345API void
Michal Vasko086311b2016-01-08 09:53:11 +0100346nc_server_set_hello_timeout(uint16_t hello_timeout)
347{
Michal Vasko086311b2016-01-08 09:53:11 +0100348 server_opts.hello_timeout = hello_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100349}
350
Michal Vasko1a38c862016-01-15 15:50:07 +0100351API void
Michal Vasko086311b2016-01-08 09:53:11 +0100352nc_server_set_idle_timeout(uint16_t idle_timeout)
353{
Michal Vasko086311b2016-01-08 09:53:11 +0100354 server_opts.idle_timeout = idle_timeout;
Michal Vasko086311b2016-01-08 09:53:11 +0100355}
356
357API int
Michal Vasko1a38c862016-01-15 15:50:07 +0100358nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **session)
Michal Vasko086311b2016-01-08 09:53:11 +0100359{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100360 if (!server_opts.ctx || (fdin < 0) || (fdout < 0) || !username || !session) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100361 ERRARG;
362 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100363 }
364
365 /* prepare session structure */
Michal Vasko1a38c862016-01-15 15:50:07 +0100366 *session = calloc(1, sizeof **session);
367 if (!(*session)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100368 ERRMEM;
Michal Vasko1a38c862016-01-15 15:50:07 +0100369 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100370 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100371 (*session)->status = NC_STATUS_STARTING;
372 (*session)->side = NC_SERVER;
Michal Vasko086311b2016-01-08 09:53:11 +0100373
374 /* transport specific data */
Michal Vasko1a38c862016-01-15 15:50:07 +0100375 (*session)->ti_type = NC_TI_FD;
376 (*session)->ti.fd.in = fdin;
377 (*session)->ti.fd.out = fdout;
Michal Vasko086311b2016-01-08 09:53:11 +0100378
379 /* assign context (dicionary needed for handshake) */
Michal Vasko1a38c862016-01-15 15:50:07 +0100380 (*session)->flags = NC_SESSION_SHAREDCTX;
381 (*session)->ctx = server_opts.ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100382
Michal Vaskob48aa812016-01-18 14:13:09 +0100383 /* assign new SID atomically */
384 pthread_spin_lock(&server_opts.sid_lock);
385 (*session)->id = server_opts.new_session_id++;
386 pthread_spin_unlock(&server_opts.sid_lock);
387
Michal Vasko086311b2016-01-08 09:53:11 +0100388 /* NETCONF handshake */
Michal Vasko1a38c862016-01-15 15:50:07 +0100389 if (nc_handshake(*session)) {
Michal Vasko086311b2016-01-08 09:53:11 +0100390 goto fail;
391 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100392 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko086311b2016-01-08 09:53:11 +0100393
Michal Vasko1a38c862016-01-15 15:50:07 +0100394 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +0100395
396fail:
Michal Vasko1a38c862016-01-15 15:50:07 +0100397 nc_session_free(*session);
398 *session = NULL;
399 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100400}
Michal Vasko9e036d52016-01-08 10:49:26 +0100401
Michal Vasko428087d2016-01-14 16:04:28 +0100402API struct nc_pollsession *
403nc_ps_new(void)
404{
405 return calloc(1, sizeof(struct nc_pollsession));
406}
407
408API void
409nc_ps_free(struct nc_pollsession *ps)
410{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100411 if (!ps) {
412 return;
413 }
414
Michal Vasko428087d2016-01-14 16:04:28 +0100415 free(ps->sessions);
416 free(ps);
417}
418
419API int
420nc_ps_add_session(struct nc_pollsession *ps, struct nc_session *session)
421{
422 if (!ps || !session) {
423 ERRARG;
424 return -1;
425 }
426
427 ++ps->session_count;
428 ps->sessions = realloc(ps->sessions, ps->session_count * sizeof *ps->sessions);
429
430 switch (session->ti_type) {
431 case NC_TI_FD:
432 ps->sessions[ps->session_count - 1].fd = session->ti.fd.in;
433 break;
434
435#ifdef ENABLE_SSH
436 case NC_TI_LIBSSH:
437 ps->sessions[ps->session_count - 1].fd = ssh_get_fd(session->ti.libssh.session);
438 break;
439#endif
440
441#ifdef ENABLE_TLS
442 case NC_TI_OPENSSL:
443 ps->sessions[ps->session_count - 1].fd = SSL_get_rfd(session->ti.tls);
444 break;
445#endif
446
447 default:
448 ERRINT;
449 return -1;
450 }
451 ps->sessions[ps->session_count - 1].events = POLLIN;
452 ps->sessions[ps->session_count - 1].revents = 0;
453 ps->sessions[ps->session_count - 1].session = session;
454
455 return 0;
456}
457
458API int
459nc_ps_del_session(struct nc_pollsession *ps, struct nc_session *session)
460{
461 uint16_t i;
462
463 if (!ps || !session) {
464 ERRARG;
465 return -1;
466 }
467
468 for (i = 0; i < ps->session_count; ++i) {
469 if (ps->sessions[i].session == session) {
470 --ps->session_count;
471 memmove(&ps->sessions[i], &ps->sessions[i + 1], ps->session_count - i);
472 return 0;
473 }
474 }
475
476 return 1;
477}
478
479/* must be called holding the session lock! */
480static NC_MSG_TYPE
481nc_recv_rpc(struct nc_session *session, struct nc_server_rpc **rpc)
482{
483 struct lyxml_elem *xml = NULL;
484 NC_MSG_TYPE msgtype;
485
486 if (!session || !rpc) {
487 ERRARG;
488 return NC_MSG_ERROR;
489 } else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_SERVER)) {
490 ERR("%s: invalid session to receive RPCs.", __func__);
491 return NC_MSG_ERROR;
492 }
493
494 msgtype = nc_read_msg(session, &xml);
495
496 switch (msgtype) {
497 case NC_MSG_RPC:
498 *rpc = malloc(sizeof **rpc);
499 (*rpc)->tree = lyd_parse_xml(server_opts.ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_RPC);
500 (*rpc)->root = xml;
501 break;
502 case NC_MSG_HELLO:
503 ERR("%s: session %u: received another <hello> message.", __func__, session->id);
504 goto error;
505 case NC_MSG_REPLY:
506 ERR("%s: session %u: received <rpc-reply> from NETCONF client.", __func__, session->id);
507 goto error;
508 case NC_MSG_NOTIF:
509 ERR("%s: session %u: received <notification> from NETCONF client.", __func__, session->id);
510 goto error;
511 default:
512 /* NC_MSG_ERROR - pass it out;
513 * NC_MSG_WOULDBLOCK and NC_MSG_NONE is not returned by nc_read_msg()
514 */
515 break;
516 }
517
518 return msgtype;
519
520error:
521 /* cleanup */
522 lyxml_free(server_opts.ctx, xml);
523
524 return NC_MSG_ERROR;
525}
526
527/* must be called holding the session lock! */
528static NC_MSG_TYPE
529nc_send_reply(struct nc_session *session, struct nc_server_rpc *rpc)
530{
531 nc_rpc_clb clb;
532 struct nc_server_reply *reply;
533 int ret;
534
535 /* no callback, reply with a not-implemented error */
536 if (!rpc->tree->schema->private) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100537 reply = nc_server_reply_err(nc_err(NC_ERR_OP_NOT_SUPPORTED, NC_ERR_TYPE_PROT));
Michal Vasko428087d2016-01-14 16:04:28 +0100538 } else {
539 clb = (nc_rpc_clb)rpc->tree->schema->private;
540 reply = clb(rpc->tree, session);
541 }
542
543 if (!reply) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100544 reply = nc_server_reply_err(nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP));
Michal Vasko428087d2016-01-14 16:04:28 +0100545 }
546
547 ret = nc_write_msg(session, NC_MSG_REPLY, rpc->root, reply);
548
549 /* special case if term_reason was set in callback, last reply was sent (needed for <close-session> if nothing else) */
550 if ((session->status == NC_STATUS_RUNNING) && (session->term_reason != NC_SESSION_TERM_NONE)) {
551 session->status = NC_STATUS_INVALID;
552 }
553
554 if (ret == -1) {
555 ERR("%s: failed to write reply.", __func__);
556 nc_server_reply_free(reply);
557 return NC_MSG_ERROR;
558 }
559 nc_server_reply_free(reply);
560
561 return NC_MSG_REPLY;
562}
563
564API int
565nc_ps_poll(struct nc_pollsession *ps, int timeout)
566{
567 int ret;
568 uint16_t i;
569 NC_MSG_TYPE msgtype;
570 struct nc_session *session;
571 struct nc_server_rpc *rpc;
572 struct timespec old_ts, new_ts;
573
574 if (!ps || !ps->session_count) {
575 ERRARG;
576 return -1;
577 }
578
579 for (i = 0; i < ps->session_count; ++i) {
580 if (ps->sessions[i].session->status != NC_STATUS_RUNNING) {
581 ERR("%s: session %u: session not running.", __func__, ps->sessions[i].session->id);
582 return -1;
583 }
584 }
585
586 if (timeout > 0) {
587 clock_gettime(CLOCK_MONOTONIC_RAW, &old_ts);
588 }
589
590retry_poll:
591 ret = poll((struct pollfd *)ps->sessions, ps->session_count, timeout);
592 if (ret < 1) {
593 return ret;
594 }
595
596 /* find the first fd with POLLIN, we don't care if there are more */
597 for (i = 0; i < ps->session_count; ++i) {
598 if (ps->sessions[i].revents & POLLIN) {
599#ifdef ENABLE_SSH
600 if (ps->sessions[i].session->ti_type == NC_TI_LIBSSH) {
601 /* things are not that simple with SSH, we need to check the channel */
602 ret = ssh_channel_poll_timeout(ps->sessions[i].session->ti.libssh.channel, 0, 0);
603 /* not this one */
604 if (!ret) {
605 if (i == ps->session_count - 1) {
606 /* last session and it is not the right channel, ... */
607 if (timeout > 0) {
608 /* ... decrease timeout, wait it all out and try again, last time */
609 clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
610
611 timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
612 timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
613 if (timeout < 0) {
614 ERRINT;
615 return -1;
616 }
617
618 old_ts = new_ts;
619 } else if (!timeout) {
620 /* ... timeout is 0, so that is it */
621 return 0;
622 } else {
623 /* ... retry polling reasonable time apart */
624 usleep(NC_TIMEOUT_STEP);
625 goto retry_poll;
626 }
627 }
628 /* check other sessions */
629 continue;
630 } else if (ret == SSH_ERROR) {
631 ERR("%s: session %u: SSH channel error (%s).", __func__, ps->sessions[i].session->id,
632 ssh_get_error(ps->sessions[i].session->ti.libssh.session));
633 ps->sessions[i].session->status = NC_STATUS_INVALID;
634 ps->sessions[i].session->term_reason = NC_SESSION_TERM_OTHER;
635 return 2;
636 } else if (ret == SSH_EOF) {
637 ERR("%s: session %u: communication channel unexpectedly closed (libssh).",
638 __func__, ps->sessions[i].session->id);
639 ps->sessions[i].session->status = NC_STATUS_INVALID;
640 ps->sessions[i].session->term_reason = NC_SESSION_TERM_DROPPED;
641 return 2;
642 }
643 }
644#endif /* ENABLE_SSH */
645
646 break;
647 }
648 }
649
650 if (i == ps->session_count) {
651 ERRINT;
652 return -1;
653 }
654
655 /* this is the session with some data available for reading */
656 session = ps->sessions[i].session;
657
658 if (timeout > 0) {
659 clock_gettime(CLOCK_MONOTONIC_RAW, &new_ts);
660
661 /* subtract elapsed time */
662 timeout -= (new_ts.tv_sec - old_ts.tv_sec) * 1000;
663 timeout -= (new_ts.tv_nsec - old_ts.tv_nsec) / 1000000;
664 if (timeout < 0) {
665 ERRINT;
666 return -1;
667 }
668 }
669
670 /* reading an RPC and sending a reply must be atomic */
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100671 ret = nc_timedlock(session->ti_lock, timeout, NULL);
672 if (ret != 1) {
673 /* error or timeout */
674 return ret;
Michal Vasko428087d2016-01-14 16:04:28 +0100675 }
676
677 msgtype = nc_recv_rpc(session, &rpc);
678 if (msgtype == NC_MSG_ERROR) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100679 pthread_mutex_unlock(session->ti_lock);
Michal Vasko428087d2016-01-14 16:04:28 +0100680 if (session->status != NC_STATUS_RUNNING) {
681 return 2;
682 }
683 return -1;
684 }
685
686 /* process RPC */
687 msgtype = nc_send_reply(session, rpc);
688
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100689 pthread_mutex_unlock(session->ti_lock);
Michal Vasko428087d2016-01-14 16:04:28 +0100690
691 if (msgtype == NC_MSG_ERROR) {
692 nc_server_rpc_free(rpc);
693 if (session->status != NC_STATUS_RUNNING) {
694 return 2;
695 }
696 return -1;
697 }
698
699 nc_server_rpc_free(rpc);
700 return 1;
701}
702
Michal Vasko9e036d52016-01-08 10:49:26 +0100703#if defined(ENABLE_SSH) || defined(ENABLE_TLS)
704
705API int
706nc_server_add_bind_listen(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
707{
708 int sock;
709
710 if (!address || !port || ((ti != NC_TI_LIBSSH) && (ti != NC_TI_OPENSSL))) {
711 ERRARG;
712 return -1;
713 }
714
715 sock = nc_sock_listen(address, port);
716 if (sock == -1) {
717 return -1;
718 }
719
Michal Vaskob48aa812016-01-18 14:13:09 +0100720 /* LOCK */
721 pthread_mutex_lock(&server_opts.bind_lock);
722
Michal Vasko9e036d52016-01-08 10:49:26 +0100723 ++server_opts.bind_count;
724 server_opts.binds = realloc(server_opts.binds, server_opts.bind_count * sizeof *server_opts.binds);
725
726 server_opts.binds[server_opts.bind_count - 1].address = strdup(address);
727 server_opts.binds[server_opts.bind_count - 1].port = port;
728 server_opts.binds[server_opts.bind_count - 1].sock = sock;
729 server_opts.binds[server_opts.bind_count - 1].ti = ti;
730
Michal Vaskob48aa812016-01-18 14:13:09 +0100731 /* UNLOCK */
732 pthread_mutex_unlock(&server_opts.bind_lock);
733
Michal Vasko9e036d52016-01-08 10:49:26 +0100734 return 0;
735}
736
737API int
738nc_server_del_bind(const char *address, uint16_t port, NC_TRANSPORT_IMPL ti)
739{
740 uint32_t i;
741 int ret = -1;
742
Michal Vaskob48aa812016-01-18 14:13:09 +0100743 /* LOCK */
744 pthread_mutex_lock(&server_opts.bind_lock);
745
Michal Vasko1a38c862016-01-15 15:50:07 +0100746 if (!address && !port && !ti) {
747 for (i = 0; i < server_opts.bind_count; ++i) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100748 close(server_opts.binds[i].sock);
749 free(server_opts.binds[i].address);
750
Michal Vasko9e036d52016-01-08 10:49:26 +0100751 ret = 0;
752 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100753 free(server_opts.binds);
754 server_opts.binds = NULL;
755 server_opts.bind_count = 0;
756 } else {
757 for (i = 0; i < server_opts.bind_count; ++i) {
758 if ((!address || !strcmp(server_opts.binds[i].address, address))
759 && (!port || (server_opts.binds[i].port == port))
760 && (!ti || (server_opts.binds[i].ti == ti))) {
761 close(server_opts.binds[i].sock);
762 free(server_opts.binds[i].address);
763
764 --server_opts.bind_count;
765 memmove(&server_opts.binds[i], &server_opts.binds[i + 1], (server_opts.bind_count - i) * sizeof *server_opts.binds);
766
767 ret = 0;
768 }
769 }
Michal Vasko9e036d52016-01-08 10:49:26 +0100770 }
771
Michal Vaskob48aa812016-01-18 14:13:09 +0100772 /* UNLOCK */
773 pthread_mutex_unlock(&server_opts.bind_lock);
774
Michal Vasko9e036d52016-01-08 10:49:26 +0100775 return ret;
776}
777
Michal Vasko1a38c862016-01-15 15:50:07 +0100778API int
779nc_accept(int timeout, struct nc_session **session)
Michal Vasko9e036d52016-01-08 10:49:26 +0100780{
781 NC_TRANSPORT_IMPL ti;
Michal Vasko1a38c862016-01-15 15:50:07 +0100782 int sock, ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100783 char *host;
784 uint16_t port;
Michal Vasko9e036d52016-01-08 10:49:26 +0100785
Michal Vasko1a38c862016-01-15 15:50:07 +0100786 if (!server_opts.ctx || !server_opts.binds || !session) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100787 ERRARG;
Michal Vasko1a38c862016-01-15 15:50:07 +0100788 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100789 }
790
Michal Vaskob48aa812016-01-18 14:13:09 +0100791 /* LOCK */
792 pthread_mutex_lock(&server_opts.bind_lock);
793
794 ret = nc_sock_accept(server_opts.binds, server_opts.bind_count, timeout, &ti, &host, &port);
795
796 /* UNLOCK */
797 pthread_mutex_unlock(&server_opts.bind_lock);
798
799 if (ret < 1) {
800 return ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100801 }
Michal Vaskob48aa812016-01-18 14:13:09 +0100802 sock = ret;
Michal Vasko9e036d52016-01-08 10:49:26 +0100803
Michal Vasko1a38c862016-01-15 15:50:07 +0100804 *session = calloc(1, sizeof **session);
Michal Vasko9e036d52016-01-08 10:49:26 +0100805 if (!session) {
806 ERRMEM;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100807 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100808 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100809 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100810 (*session)->status = NC_STATUS_STARTING;
811 (*session)->side = NC_SERVER;
812 (*session)->ctx = server_opts.ctx;
813 (*session)->flags = NC_SESSION_SHAREDCTX;
814 (*session)->host = lydict_insert_zc(server_opts.ctx, host);
815 (*session)->port = port;
Michal Vasko9e036d52016-01-08 10:49:26 +0100816
817 /* transport lock */
Michal Vasko1a38c862016-01-15 15:50:07 +0100818 (*session)->ti_lock = malloc(sizeof *(*session)->ti_lock);
819 if (!(*session)->ti_lock) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100820 ERRMEM;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100821 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100822 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100823 goto fail;
824 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100825 pthread_mutex_init((*session)->ti_lock, NULL);
Michal Vasko9e036d52016-01-08 10:49:26 +0100826
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100827 /* sock gets assigned to session or closed */
Michal Vasko9e036d52016-01-08 10:49:26 +0100828 if (ti == NC_TI_LIBSSH) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100829 ret = nc_accept_ssh_session(*session, sock, timeout);
830 if (ret < 1) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100831 goto fail;
832 }
833 } else if (ti == NC_TI_OPENSSL) {
Michal Vasko1a38c862016-01-15 15:50:07 +0100834 ret = nc_accept_tls_session(*session, sock, timeout);
835 if (ret < 1) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100836 goto fail;
837 }
838 } else {
839 ERRINT;
Michal Vaskoc14e3c82016-01-11 16:14:30 +0100840 close(sock);
Michal Vasko1a38c862016-01-15 15:50:07 +0100841 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100842 goto fail;
843 }
844
Michal Vaskob48aa812016-01-18 14:13:09 +0100845 /* assign new SID atomically */
846 /* LOCK */
847 pthread_spin_lock(&server_opts.sid_lock);
848 (*session)->id = server_opts.new_session_id++;
849 /* UNLOCK */
850 pthread_spin_unlock(&server_opts.sid_lock);
851
Michal Vasko9e036d52016-01-08 10:49:26 +0100852 /* NETCONF handshake */
Michal Vasko1a38c862016-01-15 15:50:07 +0100853 if (nc_handshake(*session)) {
854 ret = -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100855 goto fail;
856 }
Michal Vasko1a38c862016-01-15 15:50:07 +0100857 (*session)->status = NC_STATUS_RUNNING;
Michal Vasko9e036d52016-01-08 10:49:26 +0100858
Michal Vasko1a38c862016-01-15 15:50:07 +0100859 return 1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100860
861fail:
Michal Vasko1a38c862016-01-15 15:50:07 +0100862 nc_session_free(*session);
863 *session = NULL;
864 return -1;
Michal Vasko9e036d52016-01-08 10:49:26 +0100865}
866
867#endif /* ENABLE_SSH || ENABLE_TLS */