blob: 71cd067cb5613546ab369083dcfe4b63f5234465 [file] [log] [blame]
Michal Vasko086311b2016-01-08 09:53:11 +01001/**
2 * \file session_client.c
3 * \author Michal Vasko <mvasko@cesnet.cz>
4 * \brief libnetconf2 session client 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 <assert.h>
24#include <errno.h>
25#include <fcntl.h>
26#include <netdb.h>
27#include <pthread.h>
28#include <stdlib.h>
29#include <string.h>
30#include <sys/socket.h>
31#include <sys/stat.h>
32#include <sys/types.h>
33#include <unistd.h>
34#include <arpa/inet.h>
35#include <poll.h>
36
37#include <libyang/libyang.h>
38
39#include "session_client.h"
40#include "libnetconf.h"
41#include "messages_p.h"
42
43static char *schema_searchpath = NULL;
44
45API int
46nc_schema_searchpath(const char *path)
47{
48 if (schema_searchpath) {
49 free(schema_searchpath);
50 }
51 schema_searchpath = strdup(path);
52
53 return schema_searchpath ? 0 : 1;
54}
55
56API NC_STATUS
57nc_session_get_status(const struct nc_session *session)
58{
59 return session->status;
60}
61
62API uint32_t
63nc_session_get_id(const struct nc_session *session)
64{
65 return session->id;
66}
67
68API NC_TRANSPORT_IMPL
69nc_session_get_ti(const struct nc_session *session)
70{
71 return session->ti_type;
72}
73
74API const char *
75nc_session_get_username(const struct nc_session *session)
76{
77 return session->username;
78}
79
80API const char *
81nc_session_get_host(const struct nc_session *session)
82{
83 return session->host;
84}
85
86API uint16_t
87nc_session_get_port(const struct nc_session *session)
88{
89 return session->port;
90}
91
92API const char **
93nc_session_get_cpblts(const struct nc_session *session)
94{
95 return session->cpblts;
96}
97
98API const char *
99nc_session_cpblt(const struct nc_session *session, const char *capab)
100{
101 int i, len;
102
103 len = strlen(capab);
104 for (i = 0; session->cpblts[i]; ++i) {
105 if (!strncmp(session->cpblts[i], capab, len)) {
106 return session->cpblts[i];
107 }
108 }
109
110 return NULL;
111}
112
113/* SCHEMAS_DIR not used */
114static int
115ctx_check_and_load_model(struct nc_session *session, const char *cpblt)
116{
117 const struct lys_module *module;
118 char *ptr, *ptr2;
119 char *model_name, *revision = NULL, *features = NULL;
120
121 /* parse module */
122 ptr = strstr(cpblt, "module=");
123 if (!ptr) {
124 WRN("Unknown capability \"%s\" could not be parsed.", cpblt);
125 return 1;
126 }
127 ptr += 7;
128 ptr2 = strchr(ptr, '&');
129 if (!ptr2) {
130 ptr2 = ptr + strlen(ptr);
131 }
132 model_name = strndup(ptr, ptr2 - ptr);
133
134 /* parse revision */
135 ptr = strstr(cpblt, "revision=");
136 if (ptr) {
137 ptr += 9;
138 ptr2 = strchr(ptr, '&');
139 if (!ptr2) {
140 ptr2 = ptr + strlen(ptr);
141 }
142 revision = strndup(ptr, ptr2 - ptr);
143 }
144
145 /* load module if needed */
146 module = ly_ctx_get_module(session->ctx, model_name, revision);
147 if (!module) {
148 module = ly_ctx_load_module(session->ctx, model_name, revision);
149 }
150
151 free(model_name);
152 free(revision);
153 if (!module) {
154 return 1;
155 }
156
157 /* parse features */
158 ptr = strstr(cpblt, "features=");
159 if (ptr) {
160 ptr += 9;
161 ptr2 = strchr(ptr, '&');
162 if (!ptr2) {
163 ptr2 = ptr + strlen(ptr);
164 }
165 features = strndup(ptr, ptr2 - ptr);
166 }
167
168 /* enable features */
169 if (features) {
170 /* basically manual strtok_r (to avoid macro) */
171 ptr2 = features;
172 for (ptr = features; *ptr; ++ptr) {
173 if (*ptr == ',') {
174 *ptr = '\0';
175 /* remember last feature */
176 ptr2 = ptr + 1;
177 }
178 }
179
180 ptr = features;
181 lys_features_enable(module, ptr);
182 while (ptr != ptr2) {
183 ptr += strlen(ptr) + 1;
184 lys_features_enable(module, ptr);
185 }
186
187 free(features);
188 }
189
190 return 0;
191}
192
193/* SCHEMAS_DIR used */
194static int
195ctx_check_and_load_ietf_netconf(struct ly_ctx *ctx, const char **cpblts, int from_file)
196{
197 int i;
198 const struct lys_module *ietfnc;
199
200 ietfnc = ly_ctx_get_module(ctx, "ietf-netconf", NULL);
201 if (!ietfnc) {
202 if (from_file) {
203 ietfnc = lys_parse_path(ctx, SCHEMAS_DIR"/ietf-netconf.yin", LYS_IN_YIN);
204 } else {
205 ietfnc = ly_ctx_load_module(ctx, "ietf-netconf", NULL);
206 }
207 }
208 if (!ietfnc) {
209 ERR("Loading base NETCONF schema failed.");
210 return 1;
211 }
212
213 /* set supported capabilities from ietf-netconf */
214 for (i = 0; cpblts[i]; ++i) {
215 if (!strncmp(cpblts[i], "urn:ietf:params:netconf:capability:", 35)) {
216 if (!strncmp(cpblts[i] + 35, "writable-running", 16)) {
217 lys_features_enable(ietfnc, "writable-running");
218 } else if (!strncmp(cpblts[i] + 35, "candidate", 9)) {
219 lys_features_enable(ietfnc, "candidate");
220 } else if (!strcmp(cpblts[i] + 35, "confirmed-commit:1.1")) {
221 lys_features_enable(ietfnc, "confirmed-commit");
222 } else if (!strncmp(cpblts[i] + 35, "rollback-on-error", 17)) {
223 lys_features_enable(ietfnc, "rollback-on-error");
224 } else if (!strcmp(cpblts[i] + 35, "validate:1.1")) {
225 lys_features_enable(ietfnc, "validate");
226 } else if (!strncmp(cpblts[i] + 35, "startup", 7)) {
227 lys_features_enable(ietfnc, "startup");
228 } else if (!strncmp(cpblts[i] + 35, "url", 3)) {
229 lys_features_enable(ietfnc, "url");
230 } else if (!strncmp(cpblts[i] + 35, "xpath", 5)) {
231 lys_features_enable(ietfnc, "xpath");
232 }
233 }
234 }
235
236 return 0;
237}
238
239static char *
240libyang_module_clb(const char *name, const char *revision, void *user_data, LYS_INFORMAT *format,
241 void (**free_model_data)(char *model_data))
242{
243 struct nc_session *session = (struct nc_session *)user_data;
244 struct nc_rpc *rpc;
245 struct nc_reply *reply;
246 struct nc_reply_data *data_rpl;
247 NC_MSG_TYPE msg;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100248 char *model_data = NULL, *ptr, *ptr2, *anyxml;
Michal Vasko086311b2016-01-08 09:53:11 +0100249 uint64_t msgid;
250
251 /* TODO later replace with yang to reduce model size? */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100252 rpc = nc_rpc_getschema(name, revision, "yin", NC_PARAMTYPE_CONST);
Michal Vasko086311b2016-01-08 09:53:11 +0100253 *format = LYS_IN_YIN;
254
255 while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
256 usleep(1000);
257 }
258 if (msg == NC_MSG_ERROR) {
259 ERR("Failed to send the <get-schema> RPC.");
260 nc_rpc_free(rpc);
261 return NULL;
262 }
263
264 msg = nc_recv_reply(session, rpc, msgid, 250, &reply);
265 nc_rpc_free(rpc);
266 if (msg == NC_MSG_WOULDBLOCK) {
267 ERR("Timeout for receiving reply to a <get-schema> expired.");
268 return NULL;
269 } else if (msg == NC_MSG_ERROR) {
270 ERR("Failed to receive a reply to <get-schema>.");
271 return NULL;
272 }
273
Michal Vasko05ba9df2016-01-13 14:40:27 +0100274 if (reply->type != NC_RPL_DATA) {
275 /* TODO print the error, if error */
276 ERR("Unexpected reply type to a <get-schema> RPC.");
277 nc_reply_free(reply);
278 return NULL;
279 }
280
Michal Vasko086311b2016-01-08 09:53:11 +0100281 data_rpl = (struct nc_reply_data *)reply;
282 anyxml = lyxml_serialize(((struct lyd_node_anyxml *)data_rpl->data)->value);
283 nc_reply_free(reply);
284 *free_model_data = NULL;
285
286 /* it's with the data root node, remove it */
287 if (anyxml) {
288 ptr = strchr(anyxml, '>');
289 ++ptr;
290
291 ptr2 = strrchr(anyxml, '<');
292
293 model_data = strndup(ptr, strlen(ptr) - strlen(ptr2));
294 free(anyxml);
295 }
296
297 return model_data;
298}
299
300int
301nc_ctx_check_and_fill(struct nc_session *session)
302{
303 int i, get_schema_support = 0;
304 ly_module_clb old_clb = NULL;
305 void *old_data = NULL;
306
307 assert(session->cpblts && session->ctx);
308
309 /* check if get-schema is supported */
310 for (i = 0; session->cpblts[i]; ++i) {
311 if (!strncmp(session->cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) {
312 get_schema_support = 1;
313 break;
314 }
315 }
316
317 /* get-schema is supported, load local ietf-netconf-monitoring so we can create <get-schema> RPCs */
318 if (get_schema_support && !ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL)) {
319 if (lys_parse_path(session->ctx, SCHEMAS_DIR"/ietf-netconf-monitoring.yin", LYS_IN_YIN)) {
320 /* set module retrieval using <get-schema> */
321 old_clb = ly_ctx_get_module_clb(session->ctx, &old_data);
322 ly_ctx_set_module_clb(session->ctx, &libyang_module_clb, session);
323 } else {
324 WRN("Loading NETCONF monitoring schema failed, cannot use <get-schema>.");
325 }
326 }
327
328 /* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */
329 if (ctx_check_and_load_ietf_netconf(session->ctx, session->cpblts, !get_schema_support)) {
330 if (old_clb) {
331 ly_ctx_set_module_clb(session->ctx, old_clb, old_data);
332 }
333 return 1;
334 }
335
336 /* load all other models */
337 for (i = 0; session->cpblts[i]; ++i) {
338 if (!strncmp(session->cpblts[i], "urn:ietf:params:netconf:capability", 34)
339 || !strncmp(session->cpblts[i], "urn:ietf:params:netconf:base", 28)) {
340 continue;
341 }
342
343 ctx_check_and_load_model(session, session->cpblts[i]);
344 }
345
346 if (old_clb) {
347 ly_ctx_set_module_clb(session->ctx, old_clb, old_data);
348 }
349 return 0;
350}
351
352API struct nc_session *
353nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
354{
355 struct nc_session *session = NULL;
356
357 if (fdin < 0 || fdout < 0) {
358 ERR("%s: Invalid parameter", __func__);
359 return NULL;
360 }
361
362 /* prepare session structure */
363 session = calloc(1, sizeof *session);
364 if (!session) {
365 ERRMEM;
366 return NULL;
367 }
368 session->status = NC_STATUS_STARTING;
369 session->side = NC_CLIENT;
370
371 /* transport specific data */
372 session->ti_type = NC_TI_FD;
373 session->ti.fd.in = fdin;
374 session->ti.fd.out = fdout;
375
376 /* assign context (dicionary needed for handshake) */
377 if (!ctx) {
378 ctx = ly_ctx_new(SCHEMAS_DIR);
379 } else {
380 session->flags |= NC_SESSION_SHAREDCTX;
381 }
382 session->ctx = ctx;
383
384 /* NETCONF handshake */
385 if (nc_handshake(session)) {
386 goto fail;
387 }
388 session->status = NC_STATUS_RUNNING;
389
390 if (nc_ctx_check_and_fill(session)) {
391 goto fail;
392 }
393
394 return session;
395
396fail:
397 nc_session_free(session);
398 return NULL;
399}
400
401int
402nc_connect_getsocket(const char* host, uint16_t port)
403{
404 int i, sock = -1;
405 struct addrinfo hints, *res_list, *res;
406 char port_s[6]; /* length of string representation of short int */
407
408 snprintf(port_s, 6, "%u", port);
409
410 /* Connect to a server */
411 memset(&hints, 0, sizeof hints);
412 hints.ai_family = AF_UNSPEC;
413 hints.ai_socktype = SOCK_STREAM;
414 hints.ai_protocol = IPPROTO_TCP;
415 i = getaddrinfo(host, port_s, &hints, &res_list);
416 if (i != 0) {
417 ERR("Unable to translate the host address (%s).", gai_strerror(i));
418 return -1;
419 }
420
421 for (i = 0, res = res_list; res != NULL; res = res->ai_next) {
422 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
423 if (sock == -1) {
424 /* socket was not created, try another resource */
425 i = errno;
426 goto errloop;
427 }
428
429 if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
430 /* network connection failed, try another resource */
431 i = errno;
432 close(sock);
433 sock = -1;
434 goto errloop;
435 }
436
437 /* we're done, network connection established */
438 break;
439errloop:
440 VRB("Unable to connect to %s:%s over %s (%s).", host, port_s,
441 (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", strerror(i));
442 continue;
443 }
444
445 if (sock == -1) {
446 ERR("Unable to connect to %s:%s.", host, port_s);
447 } else {
448 VRB("Successfully connected to %s:%s over %s", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
449 }
450 freeaddrinfo(res_list);
451
452 return sock;
453}
454
Michal Vasko086311b2016-01-08 09:53:11 +0100455static NC_MSG_TYPE
456get_msg(struct nc_session *session, int32_t timeout, uint64_t msgid, struct lyxml_elem **msg)
457{
458 int r;
459 char *ptr;
460 const char *str_msgid;
461 uint64_t cur_msgid;
462 struct lyxml_elem *xml;
463 struct nc_msg_cont *cont, *prev_cont, **cont_ptr;
464 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
465
466next_message:
467 if (msgtype) {
468 /* second run, wait and give a chance to nc_recv_reply() */
469 usleep(NC_TIMEOUT_STEP);
470 timeout = timeout - (NC_TIMEOUT_STEP);
471 }
472 r = session_ti_lock(session, timeout);
473 if (r > 0) {
474 /* error */
475 return NC_MSG_ERROR;
476 } else if (r < 0) {
477 /* timeout */
478 return NC_MSG_WOULDBLOCK;
479 }
480
481 /* try to get notification from the session's queue */
482 if (!msgid && session->notifs) {
483 cont = session->notifs;
484 session->notifs = cont->next;
485
486 session_ti_unlock(session);
487
488 *msg = cont->msg;
489 free(cont);
490
491 return NC_MSG_NOTIF;
492 }
493
494 /* try to get rpc-reply from the session's queue */
495 if (msgid && session->replies) {
496 prev_cont = NULL;
497 for (cont = session->replies; cont; cont = cont->next) {
498 /* errors checked in the condition below */
499 str_msgid = lyxml_get_attr(cont->msg, "message-id", NULL);
500 cur_msgid = strtoul(str_msgid, &ptr, 10);
501
502 if (cur_msgid == msgid) {
503 if (!prev_cont) {
504 session->replies = cont->next;
505 } else {
506 prev_cont->next = cont->next;
507 }
508 session_ti_unlock(session);
509
510 *msg = cont->msg;
511 free(cont);
512
513 return NC_MSG_REPLY;
514 }
515
516 prev_cont = cont;
517 }
518 }
519
520 /* read message from wire */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100521 msgtype = nc_read_msg_poll(session, timeout, &xml);
Michal Vasko086311b2016-01-08 09:53:11 +0100522
523 /* we read rpc-reply, want a notif */
524 if (!msgid && (msgtype == NC_MSG_REPLY)) {
525 /* just check that message-id is fine */
526 str_msgid = lyxml_get_attr(xml, "message-id", NULL);
527 if (!str_msgid) {
528 session_ti_unlock(session);
529 ERR("SESSION %u: Received a <rpc-reply> with no message-id, discarding.", session->id);
530 lyxml_free(session->ctx, xml);
531 goto next_message;
532 }
533 cur_msgid = strtoul(str_msgid, &ptr, 10);
534 if (ptr[0]) {
535 session_ti_unlock(session);
536 ERR("SESSION %u: Received a <rpc-reply> with an invalid message-id (\"%s\"), discarding.", session->id, str_msgid);
537 lyxml_free(session->ctx, xml);
538 goto next_message;
539 }
540
541 cont_ptr = &session->replies;
542 while (*cont_ptr) {
543 cont_ptr = &((*cont_ptr)->next);
544 }
545 *cont_ptr = malloc(sizeof **cont_ptr);
546 (*cont_ptr)->msg = xml;
547 (*cont_ptr)->next = NULL;
548 }
549
550 /* we read notif, want a rpc-reply */
551 if (msgid && (msgtype == NC_MSG_NOTIF)) {
552 if (!session->notif) {
553 session_ti_unlock(session);
554 ERR("SESSION %u: Received a <notification> but session is not subscribed.", session->id);
555 lyxml_free(session->ctx, xml);
556 goto next_message;
557 }
558
559 cont_ptr = &session->notifs;
560 while (*cont_ptr) {
561 cont_ptr = &((*cont_ptr)->next);
562 }
563 *cont_ptr = malloc(sizeof **cont_ptr);
564 (*cont_ptr)->msg = xml;
565 (*cont_ptr)->next = NULL;
566 }
567
568 session_ti_unlock(session);
569
570 switch (msgtype) {
571 case NC_MSG_NOTIF:
572 /* we want a rpc-reply */
573 if (msgid) {
574 goto next_message;
575 }
576 *msg = xml;
577 break;
578
579 case NC_MSG_REPLY:
580 /* we want a notif */
581 if (!msgid) {
582 goto next_message;
583 }
584 *msg = xml;
585 break;
586
587 case NC_MSG_HELLO:
588 ERR("SESSION %u: Received another <hello> message.", session->id);
589 lyxml_free(session->ctx, xml);
590 goto next_message;
591
592 case NC_MSG_RPC:
593 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
594 lyxml_free(session->ctx, xml);
595 goto next_message;
596
597 default:
598 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
599 * NC_MSG_NONE is not returned by nc_read_msg()
600 */
601 break;
602 }
603
604 return msgtype;
605}
606
607/* cannot strictly fail, but does not need to fill any error parameter at all */
608static void
609parse_rpc_error(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_err *err)
610{
611 struct lyxml_elem *iter, *next, *info;
612
613 LY_TREE_FOR(xml->child, iter) {
614 if (!iter->ns) {
615 if (iter->content) {
616 WRN("<rpc-error> child \"%s\" with value \"%s\" without namespace.", iter->name, iter->content);
617 } else {
618 WRN("<rpc-error> child \"%s\" without namespace.", iter->name);
619 }
620 continue;
621 } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
622 if (iter->content) {
623 WRN("<rpc-error> child \"%s\" with value \"%s\" in an unknown namespace \"%s\".",
624 iter->name, iter->content, iter->ns->value);
625 } else {
626 WRN("<rpc-error> child \"%s\" in an unknown namespace \"%s\".", iter->name, iter->ns->value);
627 }
628 continue;
629 }
630
631 if (!strcmp(iter->name, "error-type")) {
632 if (!iter->content || (strcmp(iter->content, "transport") && strcmp(iter->content, "rpc")
633 && strcmp(iter->content, "protocol") && strcmp(iter->content, "application"))) {
634 WRN("<rpc-error> <error-type> unknown value \"%s\".", (iter->content ? iter->content : ""));
635 } else if (err->type) {
636 WRN("<rpc-error> <error-type> duplicated.");
637 } else {
638 err->type = lydict_insert(ctx, iter->content, 0);
639 }
640 } else if (!strcmp(iter->name, "error-tag")) {
641 if (!iter->content || (strcmp(iter->content, "in-use") && strcmp(iter->content, "invalid-value")
642 && strcmp(iter->content, "too-big") && strcmp(iter->content, "missing-attribute")
643 && strcmp(iter->content, "bad-attribute") && strcmp(iter->content, "unknown-attribute")
644 && strcmp(iter->content, "missing-element") && strcmp(iter->content, "bad-element")
645 && strcmp(iter->content, "unknown-element") && strcmp(iter->content, "unknown-namespace")
646 && strcmp(iter->content, "access-denied") && strcmp(iter->content, "lock-denied")
647 && strcmp(iter->content, "resource-denied") && strcmp(iter->content, "rollback-failed")
648 && strcmp(iter->content, "data-exists") && strcmp(iter->content, "data-missing")
649 && strcmp(iter->content, "operation-not-supported") && strcmp(iter->content, "operation-failed")
650 && strcmp(iter->content, "malformed-message"))) {
651 WRN("<rpc-error> <error-tag> unknown value \"%s\".", (iter->content ? iter->content : ""));
652 } else if (err->tag) {
653 WRN("<rpc-error> <error-tag> duplicated.");
654 } else {
655 err->tag = lydict_insert(ctx, iter->content, 0);
656 }
657 } else if (!strcmp(iter->name, "error-severity")) {
658 if (!iter->content || (strcmp(iter->content, "error") && strcmp(iter->content, "warning"))) {
659 WRN("<rpc-error> <error-severity> unknown value \"%s\".", (iter->content ? iter->content : ""));
660 } else if (err->severity) {
661 WRN("<rpc-error> <error-severity> duplicated.");
662 } else {
663 err->severity = lydict_insert(ctx, iter->content, 0);
664 }
665 } else if (!strcmp(iter->name, "error-app-tag")) {
666 if (err->apptag) {
667 WRN("<rpc-error> <error-app-tag> duplicated.");
668 } else {
669 err->apptag = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
670 }
671 } else if (!strcmp(iter->name, "error-path")) {
672 if (err->path) {
673 WRN("<rpc-error> <error-path> duplicated.");
674 } else {
675 err->path = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
676 }
677 } else if (!strcmp(iter->name, "error-message")) {
678 if (err->message) {
679 WRN("<rpc-error> <error-message> duplicated.");
680 } else {
681 err->message_lang = lyxml_get_attr(iter, "xml:lang", NULL);
682 if (!err->message_lang) {
683 VRB("<rpc-error> <error-message> without the recommended \"xml:lang\" attribute.");
684 }
685 err->message = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
686 }
687 } else if (!strcmp(iter->name, "error-info")) {
688 LY_TREE_FOR_SAFE(iter->child, next, info) {
689 if (info->ns && !strcmp(info->ns->value, NC_NS_BASE)) {
690 if (!strcmp(info->name, "session-id")) {
691 if (err->sid) {
692 WRN("<rpc-error> <error-info> <session-id> duplicated.");
693 } else {
694 err->sid = lydict_insert(ctx, (info->content ? info->content : ""), 0);
695 }
696 } else if (!strcmp(info->name, "bad-attr")) {
697 ++err->attr_count;
698 err->attr = realloc(err->attr, err->attr_count * sizeof *err->attr);
699 err->attr[err->attr_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
700 } else if (!strcmp(info->name, "bad-element")) {
701 ++err->elem_count;
702 err->elem = realloc(err->elem, err->elem_count * sizeof *err->elem);
703 err->elem[err->elem_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
704 } else if (!strcmp(info->name, "bad-namespace")) {
705 ++err->ns_count;
706 err->ns = realloc(err->ns, err->ns_count * sizeof *err->ns);
707 err->ns[err->ns_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
708 } else {
709 if (info->content) {
710 WRN("<rpc-error> <error-info> unknown child \"%s\" with value \"%s\".",
711 info->name, info->content);
712 } else {
713 WRN("<rpc-error> <error-info> unknown child \"%s\".", info->name);
714 }
715 }
716 } else {
717 lyxml_unlink(ctx, info);
718 ++err->other_count;
719 err->other = realloc(err->other, err->other_count * sizeof *err->other);
720 err->other[err->other_count - 1] = info;
721 }
722 }
723 } else {
724 if (iter->content) {
725 WRN("<rpc-error> unknown child \"%s\" with value \"%s\".", iter->name, iter->content);
726 } else {
727 WRN("<rpc-error> unknown child \"%s\".", iter->name);
728 }
729 }
730 }
731}
732
733static struct nc_reply *
734parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_rpc *rpc)
735{
736 struct lyxml_elem *iter;
737 const struct lys_node *schema;
738 struct lyd_node *data = NULL;
739 struct nc_reply_error *error_rpl;
740 struct nc_reply_data *data_rpl;
741 struct nc_reply *reply = NULL;
742 struct nc_rpc_generic *rpc_gen;
743 int i;
744
745 if (!xml->child) {
746 ERR("An empty <rpc-reply>.");
747 return NULL;
748 }
749
750 /* rpc-error */
751 if (!strcmp(xml->child->name, "rpc-error") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
752 /* count and check elements */
753 i = 0;
754 LY_TREE_FOR(xml->child, iter) {
755 if (strcmp(iter->name, "rpc-error")) {
756 ERR("<rpc-reply> content mismatch (<rpc-error> and <%s>).", iter->name);
757 return NULL;
758 } else if (!iter->ns) {
759 ERR("<rpc-reply> content mismatch (<rpc-error> without namespace).");
760 return NULL;
761 } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
762 ERR("<rpc-reply> content mismatch (<rpc-error> with NS \"%s\").", iter->ns->value);
763 return NULL;
764 }
765 ++i;
766 }
767
768 error_rpl = malloc(sizeof *error_rpl);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100769 error_rpl->type = NC_RPL_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +0100770 error_rpl->ctx = ctx;
771 error_rpl->err = calloc(i, sizeof *error_rpl->err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100772 error_rpl->count = i;
Michal Vasko086311b2016-01-08 09:53:11 +0100773 reply = (struct nc_reply *)error_rpl;
774
775 i = 0;
776 LY_TREE_FOR(xml->child, iter) {
777 parse_rpc_error(ctx, iter, error_rpl->err + i);
778 ++i;
779 }
780
781 /* ok */
782 } else if (!strcmp(xml->child->name, "ok") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
783 if (xml->child->next) {
784 ERR("<rpc-reply> content mismatch (<ok> and <%s>).", xml->child->next->name);
785 return NULL;
786 }
787 reply = malloc(sizeof *reply);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100788 reply->type = NC_RPL_OK;
Michal Vasko086311b2016-01-08 09:53:11 +0100789
790 /* some RPC output */
791 } else {
792 switch (rpc->type) {
793 case NC_RPC_GENERIC:
794 rpc_gen = (struct nc_rpc_generic *)rpc;
795
796 if (rpc_gen->has_data) {
797 schema = rpc_gen->content.data->schema;
798 } else {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100799 data = lyd_parse_data(ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_RPC);
Michal Vasko086311b2016-01-08 09:53:11 +0100800 if (!data) {
801 ERR("Failed to parse a generic RPC XML.");
802 return NULL;
803 }
804 schema = data->schema;
805 lyd_free(data);
806 data = NULL;
807 }
808 if (!schema) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100809 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100810 return NULL;
811 }
812 break;
813
814 case NC_RPC_GETCONFIG:
815 case NC_RPC_GET:
816 /* special treatment */
817 data = lyd_parse_xml(ctx, &xml->child->child, LYD_OPT_DESTRUCT
818 | (rpc->type == NC_RPC_GETCONFIG ? LYD_OPT_GETCONFIG : LYD_OPT_GET));
819 if (!data) {
820 ERR("Failed to parse <%s> reply.", (rpc->type == NC_RPC_GETCONFIG ? "get-config" : "get"));
821 return NULL;
822 }
823 break;
824
825 case NC_RPC_GETSCHEMA:
826 schema = ly_ctx_get_node(ctx, "/ietf-netconf-monitoring:get-schema");
827 if (!schema) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100828 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100829 return NULL;
830 }
831 break;
832
833 case NC_RPC_EDIT:
834 case NC_RPC_COPY:
835 case NC_RPC_DELETE:
836 case NC_RPC_LOCK:
837 case NC_RPC_UNLOCK:
838 case NC_RPC_KILL:
839 case NC_RPC_COMMIT:
840 case NC_RPC_DISCARD:
841 case NC_RPC_CANCEL:
842 case NC_RPC_VALIDATE:
843 case NC_RPC_SUBSCRIBE:
844 /* there is no output defined */
845 ERR("Unexpected data reply (root elem \"%s\").", xml->child->name);
846 return NULL;
847 }
848
849 data_rpl = malloc(sizeof *data_rpl);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100850 data_rpl->type = NC_RPL_DATA;
Michal Vasko086311b2016-01-08 09:53:11 +0100851 if (!data) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100852 data_rpl->data = lyd_parse_xml(ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_RPCREPLY, schema);
Michal Vasko086311b2016-01-08 09:53:11 +0100853 } else {
854 /* <get>, <get-config> */
855 data_rpl->data = data;
856 }
857 if (!data_rpl->data) {
858 ERR("Failed to parse <rpc-reply>.");
859 free(data_rpl);
860 return NULL;
861 }
862 reply = (struct nc_reply *)data_rpl;
863 }
864
865 return reply;
866}
867
868API NC_MSG_TYPE
869nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int32_t timeout, struct nc_reply **reply)
870{
871 struct lyxml_elem *xml;
872 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
873
874 if (!session || !reply) {
875 ERR("%s: Invalid parameter", __func__);
876 return NC_MSG_ERROR;
877 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
878 ERR("%s: invalid session to receive RPC replies.", __func__);
879 return NC_MSG_ERROR;
880 }
881 *reply = NULL;
882
883 msgtype = get_msg(session, timeout, msgid, &xml);
884 if (msgtype == NC_MSG_WOULDBLOCK) {
885 return NC_MSG_WOULDBLOCK;
886 }
887
888 if (msgtype == NC_MSG_REPLY) {
889 *reply = parse_reply(session->ctx, xml, rpc);
890 lyxml_free(session->ctx, xml);
891 if (!(*reply)) {
892 return NC_MSG_ERROR;
893 }
894 }
895
896 return msgtype;
897}
898
899API NC_MSG_TYPE
900nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
901{
902 struct lyxml_elem *xml, *ev_time;
903 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
904
905 if (!session || !notif) {
906 ERR("%s: Invalid parameter", __func__);
907 return NC_MSG_ERROR;
908 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
909 ERR("%s: Invalid session to receive Notifications.", __func__);
910 return NC_MSG_ERROR;
911 }
912
913 msgtype = get_msg(session, timeout, 0, &xml);
914
915 if (msgtype == NC_MSG_NOTIF) {
916 *notif = calloc(1, sizeof **notif);
917
918 /* eventTime */
919 LY_TREE_FOR(xml->child, ev_time) {
920 if (!strcmp(ev_time->name, "eventTime")) {
921 (*notif)->datetime = lydict_insert(session->ctx, ev_time->content, 0);
922 /* lyd_parse does not know this element */
923 lyxml_free(session->ctx, ev_time);
924 break;
925 }
926 }
927 if (!(*notif)->datetime) {
928 ERR("%s: Notification is missing the \"eventTime\" element.", __func__);
929 goto fail;
930 }
931
932 /* notification body */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100933 (*notif)->tree = lyd_parse_xml(session->ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_NOTIF);
Michal Vasko086311b2016-01-08 09:53:11 +0100934 lyxml_free(session->ctx, xml);
935 xml = NULL;
936 if (!(*notif)->tree) {
937 ERR("%s: Failed to parse a new notification.", __func__);
938 goto fail;
939 }
940 }
941
942 return msgtype;
943
944fail:
945 lydict_remove(session->ctx, (*notif)->datetime);
946 lyd_free((*notif)->tree);
947 free(*notif);
948 *notif = NULL;
949 lyxml_free(session->ctx, xml);
950
951 return NC_MSG_ERROR;
952}
953
954API NC_MSG_TYPE
955nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int32_t timeout, uint64_t *msgid)
956{
957 NC_MSG_TYPE r;
958 struct nc_rpc_generic *rpc_gen;
959 struct nc_rpc_getconfig *rpc_gc;
960 struct nc_rpc_edit *rpc_e;
961 struct nc_rpc_copy *rpc_cp;
962 struct nc_rpc_delete *rpc_del;
963 struct nc_rpc_lock *rpc_lock;
964 struct nc_rpc_get *rpc_g;
965 struct nc_rpc_kill *rpc_k;
966 struct nc_rpc_commit *rpc_com;
967 struct nc_rpc_cancel *rpc_can;
968 struct nc_rpc_validate *rpc_val;
969 struct nc_rpc_getschema *rpc_gs;
970 struct nc_rpc_subscribe *rpc_sub;
971 struct lyd_node *data, *node;
972 const struct lys_module *ietfnc, *ietfncmon, *notifs, *ietfncwd = NULL;
973 char str[11];
974 uint64_t cur_msgid;
975
976 if (!session || !rpc) {
977 ERR("%s: Invalid parameter", __func__);
978 return NC_MSG_ERROR;
979 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
980 ERR("%s: invalid session to send RPCs.", __func__);
981 return NC_MSG_ERROR;
982 }
983
984 if ((rpc->type != NC_RPC_GETSCHEMA) && (rpc->type != NC_RPC_GENERIC) && (rpc->type != NC_RPC_SUBSCRIBE)) {
985 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
986 if (!ietfnc) {
987 ERR("%s: Missing ietf-netconf schema in context (session %u).", __func__, session->id);
988 return NC_MSG_ERROR;
989 }
990 }
991
992 switch (rpc->type) {
993 case NC_RPC_GENERIC:
994 rpc_gen = (struct nc_rpc_generic *)rpc;
995
996 if (rpc_gen->has_data) {
997 data = rpc_gen->content.data;
998 } else {
999 data = lyd_parse_data(session->ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_STRICT);
1000 }
1001 break;
1002
1003 case NC_RPC_GETCONFIG:
1004 rpc_gc = (struct nc_rpc_getconfig *)rpc;
1005
1006 data = lyd_new(NULL, ietfnc, "get-config");
1007 node = lyd_new(data, ietfnc, "source");
1008 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_gc->source], NULL);
1009 if (!node) {
1010 lyd_free(data);
1011 return NC_MSG_ERROR;
1012 }
1013 if (rpc_gc->filter) {
1014 if (rpc_gc->filter[0] == '<') {
1015 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter);
1016 lyd_insert_attr(node, "type", "subtree");
1017 } else {
1018 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1019 lyd_insert_attr(node, "type", "xpath");
1020 lyd_insert_attr(node, "select", rpc_gc->filter);
1021 }
1022 if (!node) {
1023 lyd_free(data);
1024 return NC_MSG_ERROR;
1025 }
1026 }
1027
1028 if (rpc_gc->wd_mode) {
1029 if (!ietfncwd) {
1030 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1031 if (!ietfncwd) {
1032 ERR("%s: Missing ietf-netconf-with-defaults schema in context (session %u).", __func__, session->id);
1033 return NC_MSG_ERROR;
1034 }
1035 }
1036 switch (rpc_gc->wd_mode) {
1037 case NC_WD_UNKNOWN:
1038 /* cannot get here */
1039 break;
1040 case NC_WD_ALL:
1041 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1042 break;
1043 case NC_WD_ALL_TAG:
1044 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1045 break;
1046 case NC_WD_TRIM:
1047 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1048 break;
1049 case NC_WD_EXPLICIT:
1050 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1051 break;
1052 }
1053 if (!node) {
1054 lyd_free(data);
1055 return NC_MSG_ERROR;
1056 }
1057 }
1058 break;
1059
1060 case NC_RPC_EDIT:
1061 rpc_e = (struct nc_rpc_edit *)rpc;
1062
1063 data = lyd_new(NULL, ietfnc, "edit-config");
1064 node = lyd_new(data, ietfnc, "target");
1065 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_e->target], NULL);
1066 if (!node) {
1067 lyd_free(data);
1068 return NC_MSG_ERROR;
1069 }
1070
1071 if (rpc_e->default_op) {
1072 node = lyd_new_leaf(data, ietfnc, "default-operation", rpcedit_dfltop2str[rpc_e->default_op]);
1073 if (!node) {
1074 lyd_free(data);
1075 return NC_MSG_ERROR;
1076 }
1077 }
1078
1079 if (rpc_e->test_opt) {
1080 node = lyd_new_leaf(data, ietfnc, "test-option", rpcedit_testopt2str[rpc_e->test_opt]);
1081 if (!node) {
1082 lyd_free(data);
1083 return NC_MSG_ERROR;
1084 }
1085 }
1086
1087 if (rpc_e->error_opt) {
1088 node = lyd_new_leaf(data, ietfnc, "error-option", rpcedit_erropt2str[rpc_e->error_opt]);
1089 if (!node) {
1090 lyd_free(data);
1091 return NC_MSG_ERROR;
1092 }
1093 }
1094
1095 if (rpc_e->edit_cont[0] == '<') {
1096 node = lyd_new_anyxml(data, ietfnc, "config", rpc_e->edit_cont);
1097 } else {
1098 node = lyd_new_leaf(data, ietfnc, "url", rpc_e->edit_cont);
1099 }
1100 if (!node) {
1101 lyd_free(data);
1102 return NC_MSG_ERROR;
1103 }
1104 break;
1105
1106 case NC_RPC_COPY:
1107 rpc_cp = (struct nc_rpc_copy *)rpc;
1108
1109 data = lyd_new(NULL, ietfnc, "copy-config");
1110 node = lyd_new(data, ietfnc, "target");
1111 if (rpc_cp->url_trg) {
1112 node = lyd_new_leaf(node, ietfnc, "url", rpc_cp->url_trg);
1113 } else {
1114 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_cp->target], NULL);
1115 }
1116 if (!node) {
1117 lyd_free(data);
1118 return NC_MSG_ERROR;
1119 }
1120
1121 node = lyd_new(data, ietfnc, "source");
1122 if (rpc_cp->url_config_src) {
1123 if (rpc_cp->url_config_src[0] == '<') {
1124 node = lyd_new_anyxml(node, ietfnc, "config", rpc_cp->url_config_src);
1125 } else {
1126 node = lyd_new_leaf(node, ietfnc, "url", rpc_cp->url_config_src);
1127 }
1128 } else {
1129 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_cp->source], NULL);
1130 }
1131 if (!node) {
1132 lyd_free(data);
1133 return NC_MSG_ERROR;
1134 }
1135
1136 if (rpc_cp->wd_mode) {
1137 if (!ietfncwd) {
1138 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1139 if (!ietfncwd) {
1140 ERR("%s: Missing ietf-netconf-with-defaults schema in context (session %u).", __func__, session->id);
1141 return NC_MSG_ERROR;
1142 }
1143 }
1144 switch (rpc_cp->wd_mode) {
1145 case NC_WD_UNKNOWN:
1146 /* cannot get here */
1147 break;
1148 case NC_WD_ALL:
1149 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1150 break;
1151 case NC_WD_ALL_TAG:
1152 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1153 break;
1154 case NC_WD_TRIM:
1155 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1156 break;
1157 case NC_WD_EXPLICIT:
1158 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1159 break;
1160 }
1161 if (!node) {
1162 lyd_free(data);
1163 return NC_MSG_ERROR;
1164 }
1165 }
1166 break;
1167
1168 case NC_RPC_DELETE:
1169 rpc_del = (struct nc_rpc_delete *)rpc;
1170
1171 data = lyd_new(NULL, ietfnc, "delete-config");
1172 node = lyd_new(data, ietfnc, "target");
1173 if (rpc_del->url) {
1174 node = lyd_new_leaf(node, ietfnc, "url", rpc_del->url);
1175 } else {
1176 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_del->target], NULL);
1177 }
1178 if (!node) {
1179 lyd_free(data);
1180 return NC_MSG_ERROR;
1181 }
1182 break;
1183
1184 case NC_RPC_LOCK:
1185 rpc_lock = (struct nc_rpc_lock *)rpc;
1186
1187 data = lyd_new(NULL, ietfnc, "lock");
1188 node = lyd_new(data, ietfnc, "target");
1189 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
1190 if (!node) {
1191 lyd_free(data);
1192 return NC_MSG_ERROR;
1193 }
1194 break;
1195
1196 case NC_RPC_UNLOCK:
1197 rpc_lock = (struct nc_rpc_lock *)rpc;
1198
1199 data = lyd_new(NULL, ietfnc, "unlock");
1200 node = lyd_new(data, ietfnc, "target");
1201 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
1202 if (!node) {
1203 lyd_free(data);
1204 return NC_MSG_ERROR;
1205 }
1206 break;
1207
1208 case NC_RPC_GET:
1209 rpc_g = (struct nc_rpc_get *)rpc;
1210
1211 data = lyd_new(NULL, ietfnc, "get");
1212 if (rpc_g->filter) {
1213 if (rpc_g->filter[0] == '<') {
1214 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_g->filter);
1215 lyd_insert_attr(node, "type", "subtree");
1216 } else {
1217 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1218 lyd_insert_attr(node, "type", "xpath");
1219 lyd_insert_attr(node, "select", rpc_g->filter);
1220 }
1221 if (!node) {
1222 lyd_free(data);
1223 return NC_MSG_ERROR;
1224 }
1225 }
1226
1227 if (rpc_g->wd_mode) {
1228 if (!ietfncwd) {
1229 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1230 if (!ietfncwd) {
1231 ERR("%s: Missing ietf-netconf-with-defaults schema in context (session %u).", __func__, session->id);
1232 return NC_MSG_ERROR;
1233 }
1234 }
1235 switch (rpc_g->wd_mode) {
1236 case NC_WD_UNKNOWN:
1237 /* cannot get here */
1238 break;
1239 case NC_WD_ALL:
1240 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1241 break;
1242 case NC_WD_ALL_TAG:
1243 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1244 break;
1245 case NC_WD_TRIM:
1246 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1247 break;
1248 case NC_WD_EXPLICIT:
1249 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1250 break;
1251 }
1252 if (!node) {
1253 lyd_free(data);
1254 return NC_MSG_ERROR;
1255 }
1256 }
1257 break;
1258
1259 case NC_RPC_KILL:
1260 rpc_k = (struct nc_rpc_kill *)rpc;
1261
1262 data = lyd_new(NULL, ietfnc, "kill-session");
1263 sprintf(str, "%u", rpc_k->sid);
1264 lyd_new_leaf(data, ietfnc, "session-id", str);
1265 break;
1266
1267 case NC_RPC_COMMIT:
1268 rpc_com = (struct nc_rpc_commit *)rpc;
1269
1270 data = lyd_new(NULL, ietfnc, "commit");
1271 if (rpc_com->confirmed) {
1272 lyd_new_leaf(data, ietfnc, "confirmed", NULL);
1273 }
1274
1275 if (rpc_com->confirm_timeout) {
1276 sprintf(str, "%u", rpc_com->confirm_timeout);
1277 lyd_new_leaf(data, ietfnc, "confirm-timeout", str);
1278 }
1279
1280 if (rpc_com->persist) {
1281 node = lyd_new_leaf(data, ietfnc, "persist", rpc_com->persist);
1282 if (!node) {
1283 lyd_free(data);
1284 return NC_MSG_ERROR;
1285 }
1286 }
1287
1288 if (rpc_com->persist_id) {
1289 node = lyd_new_leaf(data, ietfnc, "persist-id", rpc_com->persist_id);
1290 if (!node) {
1291 lyd_free(data);
1292 return NC_MSG_ERROR;
1293 }
1294 }
1295 break;
1296
1297 case NC_RPC_DISCARD:
1298 data = lyd_new(NULL, ietfnc, "discard-changes");
1299 break;
1300
1301 case NC_RPC_CANCEL:
1302 rpc_can = (struct nc_rpc_cancel *)rpc;
1303
1304 data = lyd_new(NULL, ietfnc, "cancel-commit");
1305 if (rpc_can->persist_id) {
1306 node = lyd_new_leaf(data, ietfnc, "persist-id", rpc_can->persist_id);
1307 if (!node) {
1308 lyd_free(data);
1309 return NC_MSG_ERROR;
1310 }
1311 }
1312 break;
1313
1314 case NC_RPC_VALIDATE:
1315 rpc_val = (struct nc_rpc_validate *)rpc;
1316
1317 data = lyd_new(NULL, ietfnc, "validate");
1318 node = lyd_new(data, ietfnc, "source");
1319 if (rpc_val->url_config_src) {
1320 if (rpc_val->url_config_src[0] == '<') {
1321 node = lyd_new_anyxml(node, ietfnc, "config", rpc_val->url_config_src);
1322 } else {
1323 node = lyd_new_leaf(node, ietfnc, "url", rpc_val->url_config_src);
1324 }
1325 } else {
1326 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_val->source], NULL);
1327 }
1328 if (!node) {
1329 lyd_free(data);
1330 return NC_MSG_ERROR;
1331 }
1332 break;
1333
1334 case NC_RPC_GETSCHEMA:
1335 ietfncmon = ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL);
1336 if (!ietfncmon) {
1337 ERR("%s: Missing ietf-netconf-monitoring schema in context (session %u)", session->id);
1338 return NC_MSG_ERROR;
1339 }
1340
1341 rpc_gs = (struct nc_rpc_getschema *)rpc;
1342
1343 data = lyd_new(NULL, ietfncmon, "get-schema");
1344 node = lyd_new_leaf(data, ietfncmon, "identifier", rpc_gs->identifier);
1345 if (!node) {
1346 lyd_free(data);
1347 return NC_MSG_ERROR;
1348 }
1349 if (rpc_gs->version) {
1350 node = lyd_new_leaf(data, ietfncmon, "version", rpc_gs->version);
1351 if (!node) {
1352 lyd_free(data);
1353 return NC_MSG_ERROR;
1354 }
1355 }
1356 if (rpc_gs->format) {
1357 node = lyd_new_leaf(data, ietfncmon, "format", rpc_gs->format);
1358 if (!node) {
1359 lyd_free(data);
1360 return NC_MSG_ERROR;
1361 }
1362 }
1363 break;
1364
1365 case NC_RPC_SUBSCRIBE:
1366 notifs = ly_ctx_get_module(session->ctx, "notifications", NULL);
1367 if (!notifs) {
1368 ERR("%s: Missing notifications schema in context (session %u)", session->id);
1369 return NC_MSG_ERROR;
1370 }
1371
1372 rpc_sub = (struct nc_rpc_subscribe *)rpc;
1373
1374 data = lyd_new(NULL, notifs, "create-subscription");
1375 if (rpc_sub->stream) {
1376 node = lyd_new_leaf(data, notifs, "stream", rpc_sub->stream);
1377 if (!node) {
1378 lyd_free(data);
1379 return NC_MSG_ERROR;
1380 }
1381 }
1382
1383 if (rpc_sub->filter) {
1384 if (rpc_sub->filter[0] == '<') {
1385 node = lyd_new_anyxml(data, notifs, "filter", rpc_sub->filter);
1386 lyd_insert_attr(node, "type", "subtree");
1387 } else {
1388 node = lyd_new_anyxml(data, notifs, "filter", NULL);
1389 lyd_insert_attr(node, "type", "xpath");
1390 lyd_insert_attr(node, "select", rpc_sub->filter);
1391 }
1392 if (!node) {
1393 lyd_free(data);
1394 return NC_MSG_ERROR;
1395 }
1396 }
1397
1398 if (rpc_sub->start) {
1399 node = lyd_new_leaf(data, notifs, "startTime", rpc_sub->start);
1400 if (!node) {
1401 lyd_free(data);
1402 return NC_MSG_ERROR;
1403 }
1404 }
1405
1406 if (rpc_sub->stop) {
1407 node = lyd_new_leaf(data, notifs, "stopTime", rpc_sub->stop);
1408 if (!node) {
1409 lyd_free(data);
1410 return NC_MSG_ERROR;
1411 }
1412 }
1413 break;
1414 }
1415
1416 if (lyd_validate(data, LYD_OPT_STRICT)) {
1417 lyd_free(data);
1418 return NC_MSG_ERROR;
1419 }
1420
1421 r = session_ti_lock(session, timeout);
1422 if (r != 0) {
1423 /* error or blocking */
1424 r = NC_MSG_WOULDBLOCK;
1425 } else {
1426 /* send RPC, store its message ID */
1427 r = nc_send_msg(session, data);
1428 cur_msgid = session->msgid;
1429 }
1430 session_ti_unlock(session);
1431
1432 lyd_free(data);
1433
1434 if (r != NC_MSG_RPC) {
1435 return r;
1436 }
1437
1438 *msgid = cur_msgid;
1439 return NC_MSG_RPC;
1440}
1441
1442/* CALL HOME */
1443
1444static int
1445get_listen_socket(const char *address, uint16_t port)
1446{
1447 int sock;
1448 const int optVal = 1;
1449 const socklen_t optLen = sizeof(optVal);
1450 char is_ipv4;
1451 struct sockaddr_storage saddr;
1452
1453 struct sockaddr_in* saddr4;
1454 struct sockaddr_in6* saddr6;
1455
1456 if (!address || !port) {
1457 return -1;
1458 }
1459
1460 if (strchr(address, ':') == NULL) {
1461 is_ipv4 = 1;
1462 } else {
1463 is_ipv4 = 0;
1464 }
1465
1466 sock = socket((is_ipv4 ? AF_INET : AF_INET6), SOCK_STREAM, 0);
1467 if (sock == -1) {
1468 ERR("Could not create socket (%s)", strerror(errno));
1469 return -1;
1470 }
1471
1472 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&optVal, optLen)) {
1473 ERR("Could not set socket SO_REUSEADDR option (%s)", strerror(errno));
1474 close(sock);
1475 return -1;
1476 }
1477
1478 /* TODO may be needed
1479 if (fcntl(sock, F_SETFD, FD_CLOEXEC) != 0) {
1480 nc_verb_error("%s: fcntl failed (%s)", __func__, strerror(errno));
1481 continue;
1482 }*/
1483
1484 bzero(&saddr, sizeof(struct sockaddr_storage));
1485 if (is_ipv4) {
1486 saddr4 = (struct sockaddr_in *)&saddr;
1487
1488 saddr4->sin_family = AF_INET;
1489 saddr4->sin_port = htons(port);
1490
1491 if (inet_pton(AF_INET, address, &saddr4->sin_addr) != 1) {
1492 ERR("Failed to convert \"%s\" to IPv4 address.", address);
1493 close(sock);
1494 return -1;
1495 }
1496
1497 if (bind(sock, (struct sockaddr*)saddr4, sizeof(struct sockaddr_in)) == -1) {
1498 ERR("Could not bind \"%s\" port %d (%s).", address, port, strerror(errno));
1499 close(sock);
1500 return -1;
1501 }
1502
1503 } else {
1504 saddr6 = (struct sockaddr_in6 *)&saddr;
1505
1506 saddr6->sin6_family = AF_INET6;
1507 saddr6->sin6_port = htons(port);
1508
1509 if (inet_pton(AF_INET6, address, &saddr6->sin6_addr) != 1) {
1510 ERR("Failed to convert \"%s\" to IPv6 address.", address);
1511 close(sock);
1512 return -1;
1513 }
1514
1515 if (bind(sock, (struct sockaddr*)saddr6, sizeof(struct sockaddr_in6)) == -1) {
1516 ERR("Could not bind \"%s\" port %d (%s)", __func__, address, port, strerror(errno));
1517 close(sock);
1518 return -1;
1519 }
1520 }
1521
1522 if (listen(sock, NC_REVERSE_QUEUE)) {
1523 ERR("Unable to start listening on \"%s\" port %d (%s)", __func__, address, port, strerror(errno));
1524 close(sock);
1525 return -1;
1526 }
1527
1528 return sock;
1529}
1530
1531int
1532nc_callhome_accept_connection(uint16_t port, int32_t timeout, uint16_t *server_port, char **server_host)
1533{
1534 struct pollfd reverse_listen_socket = {-1, POLLIN, 0};
1535 int sock;
1536 struct sockaddr_storage remote;
1537 socklen_t addr_size = sizeof(remote);
1538 int status;
1539
1540 reverse_listen_socket.fd = get_listen_socket("::0", port);
1541 if (reverse_listen_socket.fd == -1) {
1542 goto fail;
1543 }
1544
1545 reverse_listen_socket.revents = 0;
1546 while (1) {
1547 DBG("Waiting %ums for incoming Call Home connections.", timeout);
1548 status = poll(&reverse_listen_socket, 1, timeout);
1549
1550 if (status == 0) {
1551 /* timeout */
1552 ERR("Timeout for Call Home listen expired.");
1553 goto fail;
1554 } else if ((status == -1) && (errno == EINTR)) {
1555 /* poll was interrupted - try it again */
1556 continue;
1557 } else if (status < 0) {
1558 /* poll failed - something wrong happened */
1559 ERR("Call Home poll failed (%s).", strerror(errno));
1560 goto fail;
1561 } else if (status > 0) {
1562 if (reverse_listen_socket.revents & (POLLHUP | POLLERR)) {
1563 /* close pipe/fd - other side already did it */
1564 ERR("Call Home listening socket was closed.");
1565 goto fail;
1566 } else if (reverse_listen_socket.revents & POLLIN) {
1567 /* accept call home */
1568 sock = accept(reverse_listen_socket.fd, (struct sockaddr *)&remote, &addr_size);
1569 break;
1570 }
1571 }
1572 }
1573
1574 /* we accepted a connection, that's it */
1575 close(reverse_listen_socket.fd);
1576
1577 /* fill some server info, if interested */
1578 if (remote.ss_family == AF_INET) {
1579 struct sockaddr_in *remote_in = (struct sockaddr_in *)&remote;
1580 if (server_port) {
1581 *server_port = ntohs(remote_in->sin_port);
1582 }
1583 if (server_host) {
1584 *server_host = malloc(INET6_ADDRSTRLEN);
1585 inet_ntop(AF_INET, &(remote_in->sin_addr), *server_host, INET6_ADDRSTRLEN);
1586 }
1587 } else if (remote.ss_family == AF_INET6) {
1588 struct sockaddr_in6 *remote_in = (struct sockaddr_in6 *)&remote;
1589 if (server_port) {
1590 *server_port = ntohs(remote_in->sin6_port);
1591 }
1592 if (server_host) {
1593 *server_host = malloc(INET6_ADDRSTRLEN);
1594 inet_ntop(AF_INET6, &(remote_in->sin6_addr), *server_host, INET6_ADDRSTRLEN);
1595 }
1596 }
1597
1598 return sock;
1599
1600fail:
1601 if (reverse_listen_socket.fd != -1) {
1602 close(reverse_listen_socket.fd);
1603 }
1604
1605 return -1;
1606}