blob: f1db4bf89c4c8de2b5c6ed77a346192017b08e9f [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
Michal Vasko086311b2016-01-08 09:53:11 +010039#include "libnetconf.h"
Michal Vasko1a38c862016-01-15 15:50:07 +010040#include "session_client.h"
Michal Vasko086311b2016-01-08 09:53:11 +010041
Michal Vasko80ef5d22016-01-18 09:21:02 +010042static const char *ncds2str[] = {NULL, "config", "url", "running", "startup", "candidate"};
43
Michal Vasko1a38c862016-01-15 15:50:07 +010044static char *schema_searchpath;
Michal Vasko086311b2016-01-08 09:53:11 +010045
46API int
47nc_schema_searchpath(const char *path)
48{
49 if (schema_searchpath) {
50 free(schema_searchpath);
51 }
Michal Vasko086311b2016-01-08 09:53:11 +010052
Michal Vasko7f1c78b2016-01-19 09:52:14 +010053 if (path) {
54 schema_searchpath = strdup(path);
55 if (!schema_searchpath) {
56 ERRMEM;
57 return 1;
58 }
59 } else {
60 schema_searchpath = NULL;
61 }
62
63 return 0;
Michal Vasko086311b2016-01-08 09:53:11 +010064}
65
Michal Vasko086311b2016-01-08 09:53:11 +010066/* SCHEMAS_DIR not used */
67static int
68ctx_check_and_load_model(struct nc_session *session, const char *cpblt)
69{
70 const struct lys_module *module;
71 char *ptr, *ptr2;
72 char *model_name, *revision = NULL, *features = NULL;
73
74 /* parse module */
75 ptr = strstr(cpblt, "module=");
76 if (!ptr) {
Michal Vaskoef578332016-01-25 13:20:09 +010077 ERR("Unknown capability \"%s\" could not be parsed.", cpblt);
78 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +010079 }
80 ptr += 7;
81 ptr2 = strchr(ptr, '&');
82 if (!ptr2) {
83 ptr2 = ptr + strlen(ptr);
84 }
85 model_name = strndup(ptr, ptr2 - ptr);
86
87 /* parse revision */
88 ptr = strstr(cpblt, "revision=");
89 if (ptr) {
90 ptr += 9;
91 ptr2 = strchr(ptr, '&');
92 if (!ptr2) {
93 ptr2 = ptr + strlen(ptr);
94 }
95 revision = strndup(ptr, ptr2 - ptr);
96 }
97
98 /* load module if needed */
99 module = ly_ctx_get_module(session->ctx, model_name, revision);
100 if (!module) {
101 module = ly_ctx_load_module(session->ctx, model_name, revision);
102 }
103
Michal Vasko086311b2016-01-08 09:53:11 +0100104 free(revision);
105 if (!module) {
Michal Vaskoef578332016-01-25 13:20:09 +0100106 WRN("Failed to load model \"%s\".", model_name);
107 free(model_name);
Michal Vasko086311b2016-01-08 09:53:11 +0100108 return 1;
109 }
Michal Vaskoef578332016-01-25 13:20:09 +0100110 free(model_name);
Michal Vasko086311b2016-01-08 09:53:11 +0100111
112 /* parse features */
113 ptr = strstr(cpblt, "features=");
114 if (ptr) {
115 ptr += 9;
116 ptr2 = strchr(ptr, '&');
117 if (!ptr2) {
118 ptr2 = ptr + strlen(ptr);
119 }
120 features = strndup(ptr, ptr2 - ptr);
121 }
122
123 /* enable features */
124 if (features) {
125 /* basically manual strtok_r (to avoid macro) */
126 ptr2 = features;
127 for (ptr = features; *ptr; ++ptr) {
128 if (*ptr == ',') {
129 *ptr = '\0';
130 /* remember last feature */
131 ptr2 = ptr + 1;
132 }
133 }
134
135 ptr = features;
136 lys_features_enable(module, ptr);
137 while (ptr != ptr2) {
138 ptr += strlen(ptr) + 1;
139 lys_features_enable(module, ptr);
140 }
141
142 free(features);
143 }
144
145 return 0;
146}
147
148/* SCHEMAS_DIR used */
149static int
150ctx_check_and_load_ietf_netconf(struct ly_ctx *ctx, const char **cpblts, int from_file)
151{
152 int i;
153 const struct lys_module *ietfnc;
154
155 ietfnc = ly_ctx_get_module(ctx, "ietf-netconf", NULL);
156 if (!ietfnc) {
157 if (from_file) {
158 ietfnc = lys_parse_path(ctx, SCHEMAS_DIR"/ietf-netconf.yin", LYS_IN_YIN);
159 } else {
160 ietfnc = ly_ctx_load_module(ctx, "ietf-netconf", NULL);
161 }
162 }
163 if (!ietfnc) {
164 ERR("Loading base NETCONF schema failed.");
165 return 1;
166 }
167
168 /* set supported capabilities from ietf-netconf */
169 for (i = 0; cpblts[i]; ++i) {
170 if (!strncmp(cpblts[i], "urn:ietf:params:netconf:capability:", 35)) {
171 if (!strncmp(cpblts[i] + 35, "writable-running", 16)) {
172 lys_features_enable(ietfnc, "writable-running");
173 } else if (!strncmp(cpblts[i] + 35, "candidate", 9)) {
174 lys_features_enable(ietfnc, "candidate");
175 } else if (!strcmp(cpblts[i] + 35, "confirmed-commit:1.1")) {
176 lys_features_enable(ietfnc, "confirmed-commit");
177 } else if (!strncmp(cpblts[i] + 35, "rollback-on-error", 17)) {
178 lys_features_enable(ietfnc, "rollback-on-error");
179 } else if (!strcmp(cpblts[i] + 35, "validate:1.1")) {
180 lys_features_enable(ietfnc, "validate");
181 } else if (!strncmp(cpblts[i] + 35, "startup", 7)) {
182 lys_features_enable(ietfnc, "startup");
183 } else if (!strncmp(cpblts[i] + 35, "url", 3)) {
184 lys_features_enable(ietfnc, "url");
185 } else if (!strncmp(cpblts[i] + 35, "xpath", 5)) {
186 lys_features_enable(ietfnc, "xpath");
187 }
188 }
189 }
190
191 return 0;
192}
193
194static char *
195libyang_module_clb(const char *name, const char *revision, void *user_data, LYS_INFORMAT *format,
196 void (**free_model_data)(char *model_data))
197{
198 struct nc_session *session = (struct nc_session *)user_data;
199 struct nc_rpc *rpc;
200 struct nc_reply *reply;
201 struct nc_reply_data *data_rpl;
202 NC_MSG_TYPE msg;
Michal Vasko05ba9df2016-01-13 14:40:27 +0100203 char *model_data = NULL, *ptr, *ptr2, *anyxml;
Michal Vasko086311b2016-01-08 09:53:11 +0100204 uint64_t msgid;
205
206 /* TODO later replace with yang to reduce model size? */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100207 rpc = nc_rpc_getschema(name, revision, "yin", NC_PARAMTYPE_CONST);
Michal Vasko086311b2016-01-08 09:53:11 +0100208 *format = LYS_IN_YIN;
209
210 while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) {
211 usleep(1000);
212 }
213 if (msg == NC_MSG_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100214 ERR("Session %u: failed to send the <get-schema> RPC.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100215 nc_rpc_free(rpc);
216 return NULL;
217 }
218
219 msg = nc_recv_reply(session, rpc, msgid, 250, &reply);
220 nc_rpc_free(rpc);
221 if (msg == NC_MSG_WOULDBLOCK) {
Michal Vaskod083db62016-01-19 10:31:29 +0100222 ERR("Session %u: timeout for receiving reply to a <get-schema> expired.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100223 return NULL;
224 } else if (msg == NC_MSG_ERROR) {
Michal Vaskod083db62016-01-19 10:31:29 +0100225 ERR("Session %u: failed to receive a reply to <get-schema>.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100226 return NULL;
227 }
228
Michal Vasko05ba9df2016-01-13 14:40:27 +0100229 if (reply->type != NC_RPL_DATA) {
230 /* TODO print the error, if error */
Michal Vaskod083db62016-01-19 10:31:29 +0100231 ERR("Session %u: unexpected reply type to a <get-schema> RPC.", session->id);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100232 nc_reply_free(reply);
233 return NULL;
234 }
235
Michal Vasko086311b2016-01-08 09:53:11 +0100236 data_rpl = (struct nc_reply_data *)reply;
237 anyxml = lyxml_serialize(((struct lyd_node_anyxml *)data_rpl->data)->value);
238 nc_reply_free(reply);
239 *free_model_data = NULL;
240
241 /* it's with the data root node, remove it */
242 if (anyxml) {
243 ptr = strchr(anyxml, '>');
244 ++ptr;
245
246 ptr2 = strrchr(anyxml, '<');
247
248 model_data = strndup(ptr, strlen(ptr) - strlen(ptr2));
249 free(anyxml);
250 }
251
252 return model_data;
253}
254
Michal Vaskoef578332016-01-25 13:20:09 +0100255/* return 0 - ok, 1 - some models failed to load, -1 - error */
Michal Vasko086311b2016-01-08 09:53:11 +0100256int
257nc_ctx_check_and_fill(struct nc_session *session)
258{
Michal Vaskoef578332016-01-25 13:20:09 +0100259 int i, get_schema_support = 0, ret = 0, r;
Michal Vasko086311b2016-01-08 09:53:11 +0100260 ly_module_clb old_clb = NULL;
261 void *old_data = NULL;
262
263 assert(session->cpblts && session->ctx);
264
265 /* check if get-schema is supported */
266 for (i = 0; session->cpblts[i]; ++i) {
267 if (!strncmp(session->cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) {
268 get_schema_support = 1;
269 break;
270 }
271 }
272
273 /* get-schema is supported, load local ietf-netconf-monitoring so we can create <get-schema> RPCs */
274 if (get_schema_support && !ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL)) {
275 if (lys_parse_path(session->ctx, SCHEMAS_DIR"/ietf-netconf-monitoring.yin", LYS_IN_YIN)) {
276 /* set module retrieval using <get-schema> */
277 old_clb = ly_ctx_get_module_clb(session->ctx, &old_data);
278 ly_ctx_set_module_clb(session->ctx, &libyang_module_clb, session);
279 } else {
280 WRN("Loading NETCONF monitoring schema failed, cannot use <get-schema>.");
281 }
282 }
283
284 /* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */
285 if (ctx_check_and_load_ietf_netconf(session->ctx, session->cpblts, !get_schema_support)) {
286 if (old_clb) {
287 ly_ctx_set_module_clb(session->ctx, old_clb, old_data);
288 }
Michal Vaskoef578332016-01-25 13:20:09 +0100289 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +0100290 }
291
292 /* load all other models */
293 for (i = 0; session->cpblts[i]; ++i) {
294 if (!strncmp(session->cpblts[i], "urn:ietf:params:netconf:capability", 34)
295 || !strncmp(session->cpblts[i], "urn:ietf:params:netconf:base", 28)) {
296 continue;
297 }
298
Michal Vaskoef578332016-01-25 13:20:09 +0100299 r = ctx_check_and_load_model(session, session->cpblts[i]);
300 if (r == -1) {
301 ret = -1;
302 break;
303 }
304
305 /* failed to load schema, but let's try to find it using user callback (or locally, if not set),
306 * if it was using get-schema */
307 if (r == 1) {
308 if (get_schema_support) {
309 VRB("Trying to load the schema from a different source.");
310 /* works even if old_clb is NULL */
311 ly_ctx_set_module_clb(session->ctx, old_clb, old_data);
312 r = ctx_check_and_load_model(session, session->cpblts[i]);
313 }
314
315 /* fail again (or no other way to try), too bad */
316 if (r) {
317 ret = 1;
318 }
319
320 /* set get-schema callback back */
321 ly_ctx_set_module_clb(session->ctx, &libyang_module_clb, session);
322 }
Michal Vasko086311b2016-01-08 09:53:11 +0100323 }
324
325 if (old_clb) {
326 ly_ctx_set_module_clb(session->ctx, old_clb, old_data);
327 }
Michal Vaskoef578332016-01-25 13:20:09 +0100328 if (ret == 1) {
329 WRN("Some models failed to be loaded, any data from these models will be ignored.");
330 }
331 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100332}
333
334API struct nc_session *
335nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
336{
Michal Vaskod083db62016-01-19 10:31:29 +0100337 struct nc_session *session;
Michal Vasko086311b2016-01-08 09:53:11 +0100338
Michal Vaskod083db62016-01-19 10:31:29 +0100339 if ((fdin < 0) || (fdout < 0)) {
340 ERRARG;
Michal Vasko086311b2016-01-08 09:53:11 +0100341 return NULL;
342 }
343
344 /* prepare session structure */
345 session = calloc(1, sizeof *session);
346 if (!session) {
347 ERRMEM;
348 return NULL;
349 }
350 session->status = NC_STATUS_STARTING;
351 session->side = NC_CLIENT;
352
353 /* transport specific data */
354 session->ti_type = NC_TI_FD;
355 session->ti.fd.in = fdin;
356 session->ti.fd.out = fdout;
357
358 /* assign context (dicionary needed for handshake) */
359 if (!ctx) {
360 ctx = ly_ctx_new(SCHEMAS_DIR);
361 } else {
362 session->flags |= NC_SESSION_SHAREDCTX;
363 }
364 session->ctx = ctx;
365
366 /* NETCONF handshake */
367 if (nc_handshake(session)) {
368 goto fail;
369 }
370 session->status = NC_STATUS_RUNNING;
371
Michal Vaskoef578332016-01-25 13:20:09 +0100372 if (nc_ctx_check_and_fill(session) == -1) {
Michal Vasko086311b2016-01-08 09:53:11 +0100373 goto fail;
374 }
375
376 return session;
377
378fail:
379 nc_session_free(session);
380 return NULL;
381}
382
383int
Michal Vaskof05562c2016-01-20 12:06:43 +0100384nc_sock_connect(const char* host, uint16_t port)
Michal Vasko086311b2016-01-08 09:53:11 +0100385{
386 int i, sock = -1;
387 struct addrinfo hints, *res_list, *res;
388 char port_s[6]; /* length of string representation of short int */
389
390 snprintf(port_s, 6, "%u", port);
391
392 /* Connect to a server */
393 memset(&hints, 0, sizeof hints);
394 hints.ai_family = AF_UNSPEC;
395 hints.ai_socktype = SOCK_STREAM;
396 hints.ai_protocol = IPPROTO_TCP;
397 i = getaddrinfo(host, port_s, &hints, &res_list);
398 if (i != 0) {
399 ERR("Unable to translate the host address (%s).", gai_strerror(i));
400 return -1;
401 }
402
403 for (i = 0, res = res_list; res != NULL; res = res->ai_next) {
404 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
405 if (sock == -1) {
406 /* socket was not created, try another resource */
407 i = errno;
408 goto errloop;
409 }
410
411 if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
412 /* network connection failed, try another resource */
413 i = errno;
414 close(sock);
415 sock = -1;
416 goto errloop;
417 }
418
419 /* we're done, network connection established */
420 break;
421errloop:
422 VRB("Unable to connect to %s:%s over %s (%s).", host, port_s,
423 (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", strerror(i));
424 continue;
425 }
426
427 if (sock == -1) {
428 ERR("Unable to connect to %s:%s.", host, port_s);
429 } else {
Michal Vaskod083db62016-01-19 10:31:29 +0100430 VRB("Successfully connected to %s:%s over %s.", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
Michal Vasko086311b2016-01-08 09:53:11 +0100431 }
432 freeaddrinfo(res_list);
433
434 return sock;
435}
436
Michal Vasko086311b2016-01-08 09:53:11 +0100437static NC_MSG_TYPE
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100438get_msg(struct nc_session *session, int timeout, uint64_t msgid, struct lyxml_elem **msg)
Michal Vasko086311b2016-01-08 09:53:11 +0100439{
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100440 int r, elapsed;
Michal Vasko086311b2016-01-08 09:53:11 +0100441 char *ptr;
442 const char *str_msgid;
443 uint64_t cur_msgid;
444 struct lyxml_elem *xml;
445 struct nc_msg_cont *cont, *prev_cont, **cont_ptr;
446 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
447
448next_message:
449 if (msgtype) {
450 /* second run, wait and give a chance to nc_recv_reply() */
451 usleep(NC_TIMEOUT_STEP);
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100452 timeout -= NC_TIMEOUT_STEP;
Michal Vasko086311b2016-01-08 09:53:11 +0100453 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100454 elapsed = 0;
455 r = nc_timedlock(session->ti_lock, timeout, &elapsed);
456 if (r == -1) {
Michal Vasko086311b2016-01-08 09:53:11 +0100457 /* error */
458 return NC_MSG_ERROR;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100459 } else if (!r) {
Michal Vasko086311b2016-01-08 09:53:11 +0100460 /* timeout */
461 return NC_MSG_WOULDBLOCK;
462 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100463 if (timeout > 0) {
464 timeout -= elapsed;
465 }
Michal Vasko086311b2016-01-08 09:53:11 +0100466
467 /* try to get notification from the session's queue */
468 if (!msgid && session->notifs) {
469 cont = session->notifs;
470 session->notifs = cont->next;
471
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100472 pthread_mutex_unlock(session->ti_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100473
474 *msg = cont->msg;
475 free(cont);
476
477 return NC_MSG_NOTIF;
478 }
479
480 /* try to get rpc-reply from the session's queue */
481 if (msgid && session->replies) {
482 prev_cont = NULL;
483 for (cont = session->replies; cont; cont = cont->next) {
484 /* errors checked in the condition below */
485 str_msgid = lyxml_get_attr(cont->msg, "message-id", NULL);
486 cur_msgid = strtoul(str_msgid, &ptr, 10);
487
488 if (cur_msgid == msgid) {
489 if (!prev_cont) {
490 session->replies = cont->next;
491 } else {
492 prev_cont->next = cont->next;
493 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100494 pthread_mutex_unlock(session->ti_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100495
496 *msg = cont->msg;
497 free(cont);
498
499 return NC_MSG_REPLY;
500 }
501
502 prev_cont = cont;
503 }
504 }
505
506 /* read message from wire */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100507 msgtype = nc_read_msg_poll(session, timeout, &xml);
Michal Vasko086311b2016-01-08 09:53:11 +0100508
509 /* we read rpc-reply, want a notif */
510 if (!msgid && (msgtype == NC_MSG_REPLY)) {
Michal Vasko11d142a2016-01-19 15:58:24 +0100511 /* just check that there is a message-id */
Michal Vasko086311b2016-01-08 09:53:11 +0100512 str_msgid = lyxml_get_attr(xml, "message-id", NULL);
513 if (!str_msgid) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100514 pthread_mutex_unlock(session->ti_lock);
Michal Vaskod083db62016-01-19 10:31:29 +0100515 ERR("Session %u: received a <rpc-reply> with no message-id, discarding.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100516 lyxml_free(session->ctx, xml);
517 goto next_message;
518 }
Michal Vasko086311b2016-01-08 09:53:11 +0100519
520 cont_ptr = &session->replies;
521 while (*cont_ptr) {
522 cont_ptr = &((*cont_ptr)->next);
523 }
524 *cont_ptr = malloc(sizeof **cont_ptr);
525 (*cont_ptr)->msg = xml;
526 (*cont_ptr)->next = NULL;
527 }
528
529 /* we read notif, want a rpc-reply */
530 if (msgid && (msgtype == NC_MSG_NOTIF)) {
531 if (!session->notif) {
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100532 pthread_mutex_unlock(session->ti_lock);
Michal Vaskod083db62016-01-19 10:31:29 +0100533 ERR("Session %u: received a <notification> but session is not subscribed.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100534 lyxml_free(session->ctx, xml);
535 goto next_message;
536 }
537
538 cont_ptr = &session->notifs;
539 while (*cont_ptr) {
540 cont_ptr = &((*cont_ptr)->next);
541 }
542 *cont_ptr = malloc(sizeof **cont_ptr);
543 (*cont_ptr)->msg = xml;
544 (*cont_ptr)->next = NULL;
545 }
546
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100547 pthread_mutex_unlock(session->ti_lock);
Michal Vasko086311b2016-01-08 09:53:11 +0100548
549 switch (msgtype) {
550 case NC_MSG_NOTIF:
551 /* we want a rpc-reply */
552 if (msgid) {
553 goto next_message;
554 }
555 *msg = xml;
556 break;
557
558 case NC_MSG_REPLY:
559 /* we want a notif */
560 if (!msgid) {
561 goto next_message;
562 }
563 *msg = xml;
564 break;
565
566 case NC_MSG_HELLO:
Michal Vaskod083db62016-01-19 10:31:29 +0100567 ERR("Session %u: received another <hello> message.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100568 lyxml_free(session->ctx, xml);
569 goto next_message;
570
571 case NC_MSG_RPC:
Michal Vaskod083db62016-01-19 10:31:29 +0100572 ERR("Session %u: received <rpc> from a NETCONF server.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100573 lyxml_free(session->ctx, xml);
574 goto next_message;
575
576 default:
577 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
578 * NC_MSG_NONE is not returned by nc_read_msg()
579 */
580 break;
581 }
582
583 return msgtype;
584}
585
586/* cannot strictly fail, but does not need to fill any error parameter at all */
587static void
588parse_rpc_error(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_err *err)
589{
590 struct lyxml_elem *iter, *next, *info;
591
592 LY_TREE_FOR(xml->child, iter) {
593 if (!iter->ns) {
594 if (iter->content) {
595 WRN("<rpc-error> child \"%s\" with value \"%s\" without namespace.", iter->name, iter->content);
596 } else {
597 WRN("<rpc-error> child \"%s\" without namespace.", iter->name);
598 }
599 continue;
600 } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
601 if (iter->content) {
602 WRN("<rpc-error> child \"%s\" with value \"%s\" in an unknown namespace \"%s\".",
603 iter->name, iter->content, iter->ns->value);
604 } else {
605 WRN("<rpc-error> child \"%s\" in an unknown namespace \"%s\".", iter->name, iter->ns->value);
606 }
607 continue;
608 }
609
610 if (!strcmp(iter->name, "error-type")) {
611 if (!iter->content || (strcmp(iter->content, "transport") && strcmp(iter->content, "rpc")
612 && strcmp(iter->content, "protocol") && strcmp(iter->content, "application"))) {
613 WRN("<rpc-error> <error-type> unknown value \"%s\".", (iter->content ? iter->content : ""));
614 } else if (err->type) {
615 WRN("<rpc-error> <error-type> duplicated.");
616 } else {
617 err->type = lydict_insert(ctx, iter->content, 0);
618 }
619 } else if (!strcmp(iter->name, "error-tag")) {
620 if (!iter->content || (strcmp(iter->content, "in-use") && strcmp(iter->content, "invalid-value")
621 && strcmp(iter->content, "too-big") && strcmp(iter->content, "missing-attribute")
622 && strcmp(iter->content, "bad-attribute") && strcmp(iter->content, "unknown-attribute")
623 && strcmp(iter->content, "missing-element") && strcmp(iter->content, "bad-element")
624 && strcmp(iter->content, "unknown-element") && strcmp(iter->content, "unknown-namespace")
625 && strcmp(iter->content, "access-denied") && strcmp(iter->content, "lock-denied")
626 && strcmp(iter->content, "resource-denied") && strcmp(iter->content, "rollback-failed")
627 && strcmp(iter->content, "data-exists") && strcmp(iter->content, "data-missing")
628 && strcmp(iter->content, "operation-not-supported") && strcmp(iter->content, "operation-failed")
629 && strcmp(iter->content, "malformed-message"))) {
630 WRN("<rpc-error> <error-tag> unknown value \"%s\".", (iter->content ? iter->content : ""));
631 } else if (err->tag) {
632 WRN("<rpc-error> <error-tag> duplicated.");
633 } else {
634 err->tag = lydict_insert(ctx, iter->content, 0);
635 }
636 } else if (!strcmp(iter->name, "error-severity")) {
637 if (!iter->content || (strcmp(iter->content, "error") && strcmp(iter->content, "warning"))) {
638 WRN("<rpc-error> <error-severity> unknown value \"%s\".", (iter->content ? iter->content : ""));
639 } else if (err->severity) {
640 WRN("<rpc-error> <error-severity> duplicated.");
641 } else {
642 err->severity = lydict_insert(ctx, iter->content, 0);
643 }
644 } else if (!strcmp(iter->name, "error-app-tag")) {
645 if (err->apptag) {
646 WRN("<rpc-error> <error-app-tag> duplicated.");
647 } else {
648 err->apptag = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
649 }
650 } else if (!strcmp(iter->name, "error-path")) {
651 if (err->path) {
652 WRN("<rpc-error> <error-path> duplicated.");
653 } else {
654 err->path = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
655 }
656 } else if (!strcmp(iter->name, "error-message")) {
657 if (err->message) {
658 WRN("<rpc-error> <error-message> duplicated.");
659 } else {
660 err->message_lang = lyxml_get_attr(iter, "xml:lang", NULL);
661 if (!err->message_lang) {
662 VRB("<rpc-error> <error-message> without the recommended \"xml:lang\" attribute.");
663 }
664 err->message = lydict_insert(ctx, (iter->content ? iter->content : ""), 0);
665 }
666 } else if (!strcmp(iter->name, "error-info")) {
667 LY_TREE_FOR_SAFE(iter->child, next, info) {
668 if (info->ns && !strcmp(info->ns->value, NC_NS_BASE)) {
669 if (!strcmp(info->name, "session-id")) {
670 if (err->sid) {
671 WRN("<rpc-error> <error-info> <session-id> duplicated.");
672 } else {
673 err->sid = lydict_insert(ctx, (info->content ? info->content : ""), 0);
674 }
675 } else if (!strcmp(info->name, "bad-attr")) {
676 ++err->attr_count;
677 err->attr = realloc(err->attr, err->attr_count * sizeof *err->attr);
678 err->attr[err->attr_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
679 } else if (!strcmp(info->name, "bad-element")) {
680 ++err->elem_count;
681 err->elem = realloc(err->elem, err->elem_count * sizeof *err->elem);
682 err->elem[err->elem_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
683 } else if (!strcmp(info->name, "bad-namespace")) {
684 ++err->ns_count;
685 err->ns = realloc(err->ns, err->ns_count * sizeof *err->ns);
686 err->ns[err->ns_count - 1] = lydict_insert(ctx, (info->content ? info->content : ""), 0);
687 } else {
688 if (info->content) {
689 WRN("<rpc-error> <error-info> unknown child \"%s\" with value \"%s\".",
690 info->name, info->content);
691 } else {
692 WRN("<rpc-error> <error-info> unknown child \"%s\".", info->name);
693 }
694 }
695 } else {
696 lyxml_unlink(ctx, info);
697 ++err->other_count;
698 err->other = realloc(err->other, err->other_count * sizeof *err->other);
699 err->other[err->other_count - 1] = info;
700 }
701 }
702 } else {
703 if (iter->content) {
704 WRN("<rpc-error> unknown child \"%s\" with value \"%s\".", iter->name, iter->content);
705 } else {
706 WRN("<rpc-error> unknown child \"%s\".", iter->name);
707 }
708 }
709 }
710}
711
712static struct nc_reply *
713parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml, struct nc_rpc *rpc)
714{
715 struct lyxml_elem *iter;
Michal Vasko0473c4c2016-01-19 10:40:06 +0100716 const struct lys_node *schema = NULL;
717 const struct lys_module *mod;
Michal Vasko086311b2016-01-08 09:53:11 +0100718 struct lyd_node *data = NULL;
Michal Vasko1a38c862016-01-15 15:50:07 +0100719 struct nc_client_reply_error *error_rpl;
Michal Vasko086311b2016-01-08 09:53:11 +0100720 struct nc_reply_data *data_rpl;
721 struct nc_reply *reply = NULL;
722 struct nc_rpc_generic *rpc_gen;
723 int i;
724
725 if (!xml->child) {
726 ERR("An empty <rpc-reply>.");
727 return NULL;
728 }
729
730 /* rpc-error */
731 if (!strcmp(xml->child->name, "rpc-error") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
732 /* count and check elements */
733 i = 0;
734 LY_TREE_FOR(xml->child, iter) {
735 if (strcmp(iter->name, "rpc-error")) {
736 ERR("<rpc-reply> content mismatch (<rpc-error> and <%s>).", iter->name);
737 return NULL;
738 } else if (!iter->ns) {
739 ERR("<rpc-reply> content mismatch (<rpc-error> without namespace).");
740 return NULL;
741 } else if (strcmp(iter->ns->value, NC_NS_BASE)) {
742 ERR("<rpc-reply> content mismatch (<rpc-error> with NS \"%s\").", iter->ns->value);
743 return NULL;
744 }
745 ++i;
746 }
747
748 error_rpl = malloc(sizeof *error_rpl);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100749 error_rpl->type = NC_RPL_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +0100750 error_rpl->err = calloc(i, sizeof *error_rpl->err);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100751 error_rpl->count = i;
Michal Vasko1a38c862016-01-15 15:50:07 +0100752 error_rpl->ctx = ctx;
Michal Vasko086311b2016-01-08 09:53:11 +0100753 reply = (struct nc_reply *)error_rpl;
754
755 i = 0;
756 LY_TREE_FOR(xml->child, iter) {
757 parse_rpc_error(ctx, iter, error_rpl->err + i);
758 ++i;
759 }
760
761 /* ok */
762 } else if (!strcmp(xml->child->name, "ok") && xml->child->ns && !strcmp(xml->child->ns->value, NC_NS_BASE)) {
763 if (xml->child->next) {
764 ERR("<rpc-reply> content mismatch (<ok> and <%s>).", xml->child->next->name);
765 return NULL;
766 }
767 reply = malloc(sizeof *reply);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100768 reply->type = NC_RPL_OK;
Michal Vasko086311b2016-01-08 09:53:11 +0100769
770 /* some RPC output */
771 } else {
772 switch (rpc->type) {
773 case NC_RPC_GENERIC:
774 rpc_gen = (struct nc_rpc_generic *)rpc;
775
776 if (rpc_gen->has_data) {
777 schema = rpc_gen->content.data->schema;
778 } else {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100779 data = lyd_parse_data(ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_RPC);
Michal Vasko086311b2016-01-08 09:53:11 +0100780 if (!data) {
781 ERR("Failed to parse a generic RPC XML.");
782 return NULL;
783 }
784 schema = data->schema;
785 lyd_free(data);
786 data = NULL;
787 }
788 if (!schema) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100789 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100790 return NULL;
791 }
792 break;
793
794 case NC_RPC_GETCONFIG:
795 case NC_RPC_GET:
796 /* special treatment */
797 data = lyd_parse_xml(ctx, &xml->child->child, LYD_OPT_DESTRUCT
798 | (rpc->type == NC_RPC_GETCONFIG ? LYD_OPT_GETCONFIG : LYD_OPT_GET));
799 if (!data) {
800 ERR("Failed to parse <%s> reply.", (rpc->type == NC_RPC_GETCONFIG ? "get-config" : "get"));
801 return NULL;
802 }
803 break;
804
805 case NC_RPC_GETSCHEMA:
Michal Vasko0473c4c2016-01-19 10:40:06 +0100806 mod = ly_ctx_get_module(ctx, "ietf-netconf-monitoring", NULL);
807 if (mod) {
808 schema = lys_get_node(mod, "/get-schema");
809 }
Michal Vasko086311b2016-01-08 09:53:11 +0100810 if (!schema) {
Michal Vasko9e036d52016-01-08 10:49:26 +0100811 ERRINT;
Michal Vasko086311b2016-01-08 09:53:11 +0100812 return NULL;
813 }
814 break;
815
816 case NC_RPC_EDIT:
817 case NC_RPC_COPY:
818 case NC_RPC_DELETE:
819 case NC_RPC_LOCK:
820 case NC_RPC_UNLOCK:
821 case NC_RPC_KILL:
822 case NC_RPC_COMMIT:
823 case NC_RPC_DISCARD:
824 case NC_RPC_CANCEL:
825 case NC_RPC_VALIDATE:
826 case NC_RPC_SUBSCRIBE:
827 /* there is no output defined */
828 ERR("Unexpected data reply (root elem \"%s\").", xml->child->name);
829 return NULL;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100830 default:
831 ERRINT;
832 return NULL;
Michal Vasko086311b2016-01-08 09:53:11 +0100833 }
834
835 data_rpl = malloc(sizeof *data_rpl);
Michal Vasko05ba9df2016-01-13 14:40:27 +0100836 data_rpl->type = NC_RPL_DATA;
Michal Vasko086311b2016-01-08 09:53:11 +0100837 if (!data) {
Michal Vasko05ba9df2016-01-13 14:40:27 +0100838 data_rpl->data = lyd_parse_xml(ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_RPCREPLY, schema);
Michal Vasko086311b2016-01-08 09:53:11 +0100839 } else {
840 /* <get>, <get-config> */
841 data_rpl->data = data;
842 }
843 if (!data_rpl->data) {
844 ERR("Failed to parse <rpc-reply>.");
845 free(data_rpl);
846 return NULL;
847 }
848 reply = (struct nc_reply *)data_rpl;
849 }
850
851 return reply;
852}
853
854API NC_MSG_TYPE
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100855nc_recv_reply(struct nc_session *session, struct nc_rpc *rpc, uint64_t msgid, int timeout, struct nc_reply **reply)
Michal Vasko086311b2016-01-08 09:53:11 +0100856{
857 struct lyxml_elem *xml;
858 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
859
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100860 if (!session || !rpc || !reply) {
861 ERRARG;
Michal Vasko086311b2016-01-08 09:53:11 +0100862 return NC_MSG_ERROR;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100863 } else if ((session->status != NC_STATUS_RUNNING) || (session->side != NC_CLIENT)) {
Michal Vaskod083db62016-01-19 10:31:29 +0100864 ERR("Session %u: invalid session to receive RPC replies.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100865 return NC_MSG_ERROR;
866 }
867 *reply = NULL;
868
869 msgtype = get_msg(session, timeout, msgid, &xml);
870 if (msgtype == NC_MSG_WOULDBLOCK) {
871 return NC_MSG_WOULDBLOCK;
872 }
873
874 if (msgtype == NC_MSG_REPLY) {
875 *reply = parse_reply(session->ctx, xml, rpc);
876 lyxml_free(session->ctx, xml);
877 if (!(*reply)) {
878 return NC_MSG_ERROR;
879 }
880 }
881
882 return msgtype;
883}
884
885API NC_MSG_TYPE
886nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
887{
888 struct lyxml_elem *xml, *ev_time;
889 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
890
891 if (!session || !notif) {
Michal Vaskod083db62016-01-19 10:31:29 +0100892 ERRARG;
Michal Vasko086311b2016-01-08 09:53:11 +0100893 return NC_MSG_ERROR;
894 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
Michal Vaskod083db62016-01-19 10:31:29 +0100895 ERR("Session %u: invalid session to receive Notifications.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100896 return NC_MSG_ERROR;
897 }
898
899 msgtype = get_msg(session, timeout, 0, &xml);
900
901 if (msgtype == NC_MSG_NOTIF) {
902 *notif = calloc(1, sizeof **notif);
903
904 /* eventTime */
905 LY_TREE_FOR(xml->child, ev_time) {
906 if (!strcmp(ev_time->name, "eventTime")) {
907 (*notif)->datetime = lydict_insert(session->ctx, ev_time->content, 0);
908 /* lyd_parse does not know this element */
909 lyxml_free(session->ctx, ev_time);
910 break;
911 }
912 }
913 if (!(*notif)->datetime) {
Michal Vaskod083db62016-01-19 10:31:29 +0100914 ERR("Session %u: notification is missing the \"eventTime\" element.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100915 goto fail;
916 }
917
918 /* notification body */
Michal Vasko05ba9df2016-01-13 14:40:27 +0100919 (*notif)->tree = lyd_parse_xml(session->ctx, &xml->child, LYD_OPT_DESTRUCT | LYD_OPT_NOTIF);
Michal Vasko086311b2016-01-08 09:53:11 +0100920 lyxml_free(session->ctx, xml);
921 xml = NULL;
922 if (!(*notif)->tree) {
Michal Vaskod083db62016-01-19 10:31:29 +0100923 ERR("Session %u: failed to parse a new notification.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100924 goto fail;
925 }
926 }
927
928 return msgtype;
929
930fail:
931 lydict_remove(session->ctx, (*notif)->datetime);
932 lyd_free((*notif)->tree);
933 free(*notif);
934 *notif = NULL;
935 lyxml_free(session->ctx, xml);
936
937 return NC_MSG_ERROR;
938}
939
940API NC_MSG_TYPE
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100941nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc, int timeout, uint64_t *msgid)
Michal Vasko086311b2016-01-08 09:53:11 +0100942{
943 NC_MSG_TYPE r;
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100944 int ret;
Michal Vasko086311b2016-01-08 09:53:11 +0100945 struct nc_rpc_generic *rpc_gen;
946 struct nc_rpc_getconfig *rpc_gc;
947 struct nc_rpc_edit *rpc_e;
948 struct nc_rpc_copy *rpc_cp;
949 struct nc_rpc_delete *rpc_del;
950 struct nc_rpc_lock *rpc_lock;
951 struct nc_rpc_get *rpc_g;
952 struct nc_rpc_kill *rpc_k;
953 struct nc_rpc_commit *rpc_com;
954 struct nc_rpc_cancel *rpc_can;
955 struct nc_rpc_validate *rpc_val;
956 struct nc_rpc_getschema *rpc_gs;
957 struct nc_rpc_subscribe *rpc_sub;
958 struct lyd_node *data, *node;
959 const struct lys_module *ietfnc, *ietfncmon, *notifs, *ietfncwd = NULL;
960 char str[11];
961 uint64_t cur_msgid;
962
Michal Vasko7f1c78b2016-01-19 09:52:14 +0100963 if (!session || !rpc || !msgid) {
964 ERRARG;
Michal Vasko086311b2016-01-08 09:53:11 +0100965 return NC_MSG_ERROR;
966 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
Michal Vaskod083db62016-01-19 10:31:29 +0100967 ERR("Session %u: invalid session to send RPCs.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100968 return NC_MSG_ERROR;
969 }
970
971 if ((rpc->type != NC_RPC_GETSCHEMA) && (rpc->type != NC_RPC_GENERIC) && (rpc->type != NC_RPC_SUBSCRIBE)) {
972 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
973 if (!ietfnc) {
Michal Vaskod083db62016-01-19 10:31:29 +0100974 ERR("Session %u: missing ietf-netconf schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +0100975 return NC_MSG_ERROR;
976 }
977 }
978
979 switch (rpc->type) {
980 case NC_RPC_GENERIC:
981 rpc_gen = (struct nc_rpc_generic *)rpc;
982
983 if (rpc_gen->has_data) {
984 data = rpc_gen->content.data;
985 } else {
986 data = lyd_parse_data(session->ctx, rpc_gen->content.xml_str, LYD_XML, LYD_OPT_STRICT);
987 }
988 break;
989
990 case NC_RPC_GETCONFIG:
991 rpc_gc = (struct nc_rpc_getconfig *)rpc;
992
993 data = lyd_new(NULL, ietfnc, "get-config");
994 node = lyd_new(data, ietfnc, "source");
995 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_gc->source], NULL);
996 if (!node) {
997 lyd_free(data);
998 return NC_MSG_ERROR;
999 }
1000 if (rpc_gc->filter) {
1001 if (rpc_gc->filter[0] == '<') {
1002 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter);
1003 lyd_insert_attr(node, "type", "subtree");
1004 } else {
1005 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1006 lyd_insert_attr(node, "type", "xpath");
1007 lyd_insert_attr(node, "select", rpc_gc->filter);
1008 }
1009 if (!node) {
1010 lyd_free(data);
1011 return NC_MSG_ERROR;
1012 }
1013 }
1014
1015 if (rpc_gc->wd_mode) {
1016 if (!ietfncwd) {
1017 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1018 if (!ietfncwd) {
Michal Vaskod083db62016-01-19 10:31:29 +01001019 ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +01001020 return NC_MSG_ERROR;
1021 }
1022 }
1023 switch (rpc_gc->wd_mode) {
1024 case NC_WD_UNKNOWN:
1025 /* cannot get here */
1026 break;
1027 case NC_WD_ALL:
1028 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1029 break;
1030 case NC_WD_ALL_TAG:
1031 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1032 break;
1033 case NC_WD_TRIM:
1034 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1035 break;
1036 case NC_WD_EXPLICIT:
1037 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1038 break;
1039 }
1040 if (!node) {
1041 lyd_free(data);
1042 return NC_MSG_ERROR;
1043 }
1044 }
1045 break;
1046
1047 case NC_RPC_EDIT:
1048 rpc_e = (struct nc_rpc_edit *)rpc;
1049
1050 data = lyd_new(NULL, ietfnc, "edit-config");
1051 node = lyd_new(data, ietfnc, "target");
1052 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_e->target], NULL);
1053 if (!node) {
1054 lyd_free(data);
1055 return NC_MSG_ERROR;
1056 }
1057
1058 if (rpc_e->default_op) {
1059 node = lyd_new_leaf(data, ietfnc, "default-operation", rpcedit_dfltop2str[rpc_e->default_op]);
1060 if (!node) {
1061 lyd_free(data);
1062 return NC_MSG_ERROR;
1063 }
1064 }
1065
1066 if (rpc_e->test_opt) {
1067 node = lyd_new_leaf(data, ietfnc, "test-option", rpcedit_testopt2str[rpc_e->test_opt]);
1068 if (!node) {
1069 lyd_free(data);
1070 return NC_MSG_ERROR;
1071 }
1072 }
1073
1074 if (rpc_e->error_opt) {
1075 node = lyd_new_leaf(data, ietfnc, "error-option", rpcedit_erropt2str[rpc_e->error_opt]);
1076 if (!node) {
1077 lyd_free(data);
1078 return NC_MSG_ERROR;
1079 }
1080 }
1081
1082 if (rpc_e->edit_cont[0] == '<') {
1083 node = lyd_new_anyxml(data, ietfnc, "config", rpc_e->edit_cont);
1084 } else {
1085 node = lyd_new_leaf(data, ietfnc, "url", rpc_e->edit_cont);
1086 }
1087 if (!node) {
1088 lyd_free(data);
1089 return NC_MSG_ERROR;
1090 }
1091 break;
1092
1093 case NC_RPC_COPY:
1094 rpc_cp = (struct nc_rpc_copy *)rpc;
1095
1096 data = lyd_new(NULL, ietfnc, "copy-config");
1097 node = lyd_new(data, ietfnc, "target");
1098 if (rpc_cp->url_trg) {
1099 node = lyd_new_leaf(node, ietfnc, "url", rpc_cp->url_trg);
1100 } else {
1101 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_cp->target], NULL);
1102 }
1103 if (!node) {
1104 lyd_free(data);
1105 return NC_MSG_ERROR;
1106 }
1107
1108 node = lyd_new(data, ietfnc, "source");
1109 if (rpc_cp->url_config_src) {
1110 if (rpc_cp->url_config_src[0] == '<') {
1111 node = lyd_new_anyxml(node, ietfnc, "config", rpc_cp->url_config_src);
1112 } else {
1113 node = lyd_new_leaf(node, ietfnc, "url", rpc_cp->url_config_src);
1114 }
1115 } else {
1116 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_cp->source], NULL);
1117 }
1118 if (!node) {
1119 lyd_free(data);
1120 return NC_MSG_ERROR;
1121 }
1122
1123 if (rpc_cp->wd_mode) {
1124 if (!ietfncwd) {
1125 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1126 if (!ietfncwd) {
Michal Vaskod083db62016-01-19 10:31:29 +01001127 ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +01001128 return NC_MSG_ERROR;
1129 }
1130 }
1131 switch (rpc_cp->wd_mode) {
1132 case NC_WD_UNKNOWN:
1133 /* cannot get here */
1134 break;
1135 case NC_WD_ALL:
1136 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1137 break;
1138 case NC_WD_ALL_TAG:
1139 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1140 break;
1141 case NC_WD_TRIM:
1142 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1143 break;
1144 case NC_WD_EXPLICIT:
1145 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1146 break;
1147 }
1148 if (!node) {
1149 lyd_free(data);
1150 return NC_MSG_ERROR;
1151 }
1152 }
1153 break;
1154
1155 case NC_RPC_DELETE:
1156 rpc_del = (struct nc_rpc_delete *)rpc;
1157
1158 data = lyd_new(NULL, ietfnc, "delete-config");
1159 node = lyd_new(data, ietfnc, "target");
1160 if (rpc_del->url) {
1161 node = lyd_new_leaf(node, ietfnc, "url", rpc_del->url);
1162 } else {
1163 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_del->target], NULL);
1164 }
1165 if (!node) {
1166 lyd_free(data);
1167 return NC_MSG_ERROR;
1168 }
1169 break;
1170
1171 case NC_RPC_LOCK:
1172 rpc_lock = (struct nc_rpc_lock *)rpc;
1173
1174 data = lyd_new(NULL, ietfnc, "lock");
1175 node = lyd_new(data, ietfnc, "target");
1176 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
1177 if (!node) {
1178 lyd_free(data);
1179 return NC_MSG_ERROR;
1180 }
1181 break;
1182
1183 case NC_RPC_UNLOCK:
1184 rpc_lock = (struct nc_rpc_lock *)rpc;
1185
1186 data = lyd_new(NULL, ietfnc, "unlock");
1187 node = lyd_new(data, ietfnc, "target");
1188 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
1189 if (!node) {
1190 lyd_free(data);
1191 return NC_MSG_ERROR;
1192 }
1193 break;
1194
1195 case NC_RPC_GET:
1196 rpc_g = (struct nc_rpc_get *)rpc;
1197
1198 data = lyd_new(NULL, ietfnc, "get");
1199 if (rpc_g->filter) {
1200 if (rpc_g->filter[0] == '<') {
1201 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_g->filter);
1202 lyd_insert_attr(node, "type", "subtree");
1203 } else {
1204 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1205 lyd_insert_attr(node, "type", "xpath");
1206 lyd_insert_attr(node, "select", rpc_g->filter);
1207 }
1208 if (!node) {
1209 lyd_free(data);
1210 return NC_MSG_ERROR;
1211 }
1212 }
1213
1214 if (rpc_g->wd_mode) {
1215 if (!ietfncwd) {
1216 ietfncwd = ly_ctx_get_module(session->ctx, "ietf-netconf-with-defaults", NULL);
1217 if (!ietfncwd) {
Michal Vaskod083db62016-01-19 10:31:29 +01001218 ERR("Session %u: missing ietf-netconf-with-defaults schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +01001219 return NC_MSG_ERROR;
1220 }
1221 }
1222 switch (rpc_g->wd_mode) {
1223 case NC_WD_UNKNOWN:
1224 /* cannot get here */
1225 break;
1226 case NC_WD_ALL:
1227 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all");
1228 break;
1229 case NC_WD_ALL_TAG:
1230 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "report-all-tagged");
1231 break;
1232 case NC_WD_TRIM:
1233 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "trim");
1234 break;
1235 case NC_WD_EXPLICIT:
1236 node = lyd_new_leaf(data, ietfncwd, "with-defaults", "explicit");
1237 break;
1238 }
1239 if (!node) {
1240 lyd_free(data);
1241 return NC_MSG_ERROR;
1242 }
1243 }
1244 break;
1245
1246 case NC_RPC_KILL:
1247 rpc_k = (struct nc_rpc_kill *)rpc;
1248
1249 data = lyd_new(NULL, ietfnc, "kill-session");
1250 sprintf(str, "%u", rpc_k->sid);
1251 lyd_new_leaf(data, ietfnc, "session-id", str);
1252 break;
1253
1254 case NC_RPC_COMMIT:
1255 rpc_com = (struct nc_rpc_commit *)rpc;
1256
1257 data = lyd_new(NULL, ietfnc, "commit");
1258 if (rpc_com->confirmed) {
1259 lyd_new_leaf(data, ietfnc, "confirmed", NULL);
1260 }
1261
1262 if (rpc_com->confirm_timeout) {
1263 sprintf(str, "%u", rpc_com->confirm_timeout);
1264 lyd_new_leaf(data, ietfnc, "confirm-timeout", str);
1265 }
1266
1267 if (rpc_com->persist) {
1268 node = lyd_new_leaf(data, ietfnc, "persist", rpc_com->persist);
1269 if (!node) {
1270 lyd_free(data);
1271 return NC_MSG_ERROR;
1272 }
1273 }
1274
1275 if (rpc_com->persist_id) {
1276 node = lyd_new_leaf(data, ietfnc, "persist-id", rpc_com->persist_id);
1277 if (!node) {
1278 lyd_free(data);
1279 return NC_MSG_ERROR;
1280 }
1281 }
1282 break;
1283
1284 case NC_RPC_DISCARD:
1285 data = lyd_new(NULL, ietfnc, "discard-changes");
1286 break;
1287
1288 case NC_RPC_CANCEL:
1289 rpc_can = (struct nc_rpc_cancel *)rpc;
1290
1291 data = lyd_new(NULL, ietfnc, "cancel-commit");
1292 if (rpc_can->persist_id) {
1293 node = lyd_new_leaf(data, ietfnc, "persist-id", rpc_can->persist_id);
1294 if (!node) {
1295 lyd_free(data);
1296 return NC_MSG_ERROR;
1297 }
1298 }
1299 break;
1300
1301 case NC_RPC_VALIDATE:
1302 rpc_val = (struct nc_rpc_validate *)rpc;
1303
1304 data = lyd_new(NULL, ietfnc, "validate");
1305 node = lyd_new(data, ietfnc, "source");
1306 if (rpc_val->url_config_src) {
1307 if (rpc_val->url_config_src[0] == '<') {
1308 node = lyd_new_anyxml(node, ietfnc, "config", rpc_val->url_config_src);
1309 } else {
1310 node = lyd_new_leaf(node, ietfnc, "url", rpc_val->url_config_src);
1311 }
1312 } else {
1313 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_val->source], NULL);
1314 }
1315 if (!node) {
1316 lyd_free(data);
1317 return NC_MSG_ERROR;
1318 }
1319 break;
1320
1321 case NC_RPC_GETSCHEMA:
1322 ietfncmon = ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL);
1323 if (!ietfncmon) {
Michal Vaskod083db62016-01-19 10:31:29 +01001324 ERR("Session %u: missing ietf-netconf-monitoring schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +01001325 return NC_MSG_ERROR;
1326 }
1327
1328 rpc_gs = (struct nc_rpc_getschema *)rpc;
1329
1330 data = lyd_new(NULL, ietfncmon, "get-schema");
1331 node = lyd_new_leaf(data, ietfncmon, "identifier", rpc_gs->identifier);
1332 if (!node) {
1333 lyd_free(data);
1334 return NC_MSG_ERROR;
1335 }
1336 if (rpc_gs->version) {
1337 node = lyd_new_leaf(data, ietfncmon, "version", rpc_gs->version);
1338 if (!node) {
1339 lyd_free(data);
1340 return NC_MSG_ERROR;
1341 }
1342 }
1343 if (rpc_gs->format) {
1344 node = lyd_new_leaf(data, ietfncmon, "format", rpc_gs->format);
1345 if (!node) {
1346 lyd_free(data);
1347 return NC_MSG_ERROR;
1348 }
1349 }
1350 break;
1351
1352 case NC_RPC_SUBSCRIBE:
1353 notifs = ly_ctx_get_module(session->ctx, "notifications", NULL);
1354 if (!notifs) {
Michal Vaskod083db62016-01-19 10:31:29 +01001355 ERR("Session %u: missing notifications schema in the context.", session->id);
Michal Vasko086311b2016-01-08 09:53:11 +01001356 return NC_MSG_ERROR;
1357 }
1358
1359 rpc_sub = (struct nc_rpc_subscribe *)rpc;
1360
1361 data = lyd_new(NULL, notifs, "create-subscription");
1362 if (rpc_sub->stream) {
1363 node = lyd_new_leaf(data, notifs, "stream", rpc_sub->stream);
1364 if (!node) {
1365 lyd_free(data);
1366 return NC_MSG_ERROR;
1367 }
1368 }
1369
1370 if (rpc_sub->filter) {
1371 if (rpc_sub->filter[0] == '<') {
1372 node = lyd_new_anyxml(data, notifs, "filter", rpc_sub->filter);
1373 lyd_insert_attr(node, "type", "subtree");
1374 } else {
1375 node = lyd_new_anyxml(data, notifs, "filter", NULL);
1376 lyd_insert_attr(node, "type", "xpath");
1377 lyd_insert_attr(node, "select", rpc_sub->filter);
1378 }
1379 if (!node) {
1380 lyd_free(data);
1381 return NC_MSG_ERROR;
1382 }
1383 }
1384
1385 if (rpc_sub->start) {
1386 node = lyd_new_leaf(data, notifs, "startTime", rpc_sub->start);
1387 if (!node) {
1388 lyd_free(data);
1389 return NC_MSG_ERROR;
1390 }
1391 }
1392
1393 if (rpc_sub->stop) {
1394 node = lyd_new_leaf(data, notifs, "stopTime", rpc_sub->stop);
1395 if (!node) {
1396 lyd_free(data);
1397 return NC_MSG_ERROR;
1398 }
1399 }
1400 break;
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001401 default:
1402 ERRINT;
1403 return NC_MSG_ERROR;
Michal Vasko086311b2016-01-08 09:53:11 +01001404 }
1405
1406 if (lyd_validate(data, LYD_OPT_STRICT)) {
1407 lyd_free(data);
1408 return NC_MSG_ERROR;
1409 }
1410
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001411 ret = nc_timedlock(session->ti_lock, timeout, NULL);
1412 if (ret == -1) {
1413 /* error */
1414 r = NC_MSG_ERROR;
1415 } else if (!ret) {
1416 /* blocking */
Michal Vasko086311b2016-01-08 09:53:11 +01001417 r = NC_MSG_WOULDBLOCK;
1418 } else {
1419 /* send RPC, store its message ID */
1420 r = nc_send_msg(session, data);
1421 cur_msgid = session->msgid;
1422 }
Michal Vasko7f1c78b2016-01-19 09:52:14 +01001423 pthread_mutex_unlock(session->ti_lock);
Michal Vasko086311b2016-01-08 09:53:11 +01001424
1425 lyd_free(data);
1426
1427 if (r != NC_MSG_RPC) {
1428 return r;
1429 }
1430
1431 *msgid = cur_msgid;
1432 return NC_MSG_RPC;
1433}
1434
1435/* CALL HOME */
1436
Michal Vasko086311b2016-01-08 09:53:11 +01001437int
Michal Vaskoc61c4492016-01-25 11:13:34 +01001438nc_sock_accept(int sock, int timeout, char **peer_host, uint16_t *peer_port)
Michal Vasko086311b2016-01-08 09:53:11 +01001439{
Michal Vaskoc61c4492016-01-25 11:13:34 +01001440 struct pollfd pfd = {-1, POLLIN, 0};
Michal Vasko086311b2016-01-08 09:53:11 +01001441 struct sockaddr_storage remote;
1442 socklen_t addr_size = sizeof(remote);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001443 int ret, status;
Michal Vasko086311b2016-01-08 09:53:11 +01001444
Michal Vaskoc61c4492016-01-25 11:13:34 +01001445 pfd.fd = sock;
1446 pfd.revents = 0;
Michal Vasko086311b2016-01-08 09:53:11 +01001447 while (1) {
1448 DBG("Waiting %ums for incoming Call Home connections.", timeout);
Michal Vaskoc61c4492016-01-25 11:13:34 +01001449 status = poll(&pfd, 1, timeout);
Michal Vasko086311b2016-01-08 09:53:11 +01001450
1451 if (status == 0) {
1452 /* timeout */
1453 ERR("Timeout for Call Home listen expired.");
Michal Vaskoc61c4492016-01-25 11:13:34 +01001454 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001455 } else if ((status == -1) && (errno == EINTR)) {
1456 /* poll was interrupted - try it again */
1457 continue;
1458 } else if (status < 0) {
1459 /* poll failed - something wrong happened */
1460 ERR("Call Home poll failed (%s).", strerror(errno));
Michal Vaskoc61c4492016-01-25 11:13:34 +01001461 return -1;
Michal Vasko086311b2016-01-08 09:53:11 +01001462 } else if (status > 0) {
Michal Vaskoc61c4492016-01-25 11:13:34 +01001463 if (pfd.revents & (POLLHUP | POLLERR)) {
Michal Vasko086311b2016-01-08 09:53:11 +01001464 /* close pipe/fd - other side already did it */
1465 ERR("Call Home listening socket was closed.");
Michal Vaskoc61c4492016-01-25 11:13:34 +01001466 return -1;
1467 } else if (pfd.revents & POLLIN) {
Michal Vasko086311b2016-01-08 09:53:11 +01001468 /* accept call home */
Michal Vaskoc61c4492016-01-25 11:13:34 +01001469 ret = accept(pfd.fd, (struct sockaddr *)&remote, &addr_size);
Michal Vasko086311b2016-01-08 09:53:11 +01001470 break;
1471 }
1472 }
1473 }
1474
Michal Vasko086311b2016-01-08 09:53:11 +01001475 /* fill some server info, if interested */
1476 if (remote.ss_family == AF_INET) {
1477 struct sockaddr_in *remote_in = (struct sockaddr_in *)&remote;
Michal Vaskof05562c2016-01-20 12:06:43 +01001478 if (peer_port) {
1479 *peer_port = ntohs(remote_in->sin_port);
Michal Vasko086311b2016-01-08 09:53:11 +01001480 }
Michal Vaskof05562c2016-01-20 12:06:43 +01001481 if (peer_host) {
1482 *peer_host = malloc(INET6_ADDRSTRLEN);
1483 inet_ntop(AF_INET, &(remote_in->sin_addr), *peer_host, INET6_ADDRSTRLEN);
Michal Vasko086311b2016-01-08 09:53:11 +01001484 }
1485 } else if (remote.ss_family == AF_INET6) {
1486 struct sockaddr_in6 *remote_in = (struct sockaddr_in6 *)&remote;
Michal Vaskof05562c2016-01-20 12:06:43 +01001487 if (peer_port) {
1488 *peer_port = ntohs(remote_in->sin6_port);
Michal Vasko086311b2016-01-08 09:53:11 +01001489 }
Michal Vaskof05562c2016-01-20 12:06:43 +01001490 if (peer_host) {
1491 *peer_host = malloc(INET6_ADDRSTRLEN);
1492 inet_ntop(AF_INET6, &(remote_in->sin6_addr), *peer_host, INET6_ADDRSTRLEN);
Michal Vasko086311b2016-01-08 09:53:11 +01001493 }
1494 }
1495
Michal Vaskoc61c4492016-01-25 11:13:34 +01001496 return ret;
Michal Vasko086311b2016-01-08 09:53:11 +01001497}