1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2019 Olof Hagsand
6 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
7
8 This file is part of CLIXON.
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14 http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21
22 Alternatively, the contents of this file may be used under the terms of
23 the GNU General Public License Version 3 or later (the "GPL"),
24 in which case the provisions of the GPL are applicable instead
25 of those above. If you wish to allow use of your version of this file only
26 under the terms of the GPL, and not to allow others to
27 use your version of this file under the terms of Apache License version 2,
28 indicate your decision by deleting the provisions above and replace them with
29 the notice and other provisions required by the GPL. If you do not delete
30 the provisions above, a recipient may use your version of this file under
31 the terms of any one of the Apache License version 2 or the GPL.
32
33 ***** END LICENSE BLOCK *****
34
35 * Yang module and feature handling
36 * @see https://tools.ietf.org/html/rfc7895
37 */
38
39 #ifdef HAVE_CONFIG_H
40 #include "clixon_config.h" /* generated by config & autoconf */
41 #endif
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <errno.h>
46 #include <limits.h>
47 #include <ctype.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <arpa/inet.h>
51 #include <regex.h>
52 #include <dirent.h>
53 #include <sys/types.h>
54 #include <fcntl.h>
55 #include <syslog.h>
56 #include <sys/stat.h>
57 #include <netinet/in.h>
58 #include <sys/param.h>
59
60 /* cligen */
61 #include <cligen/cligen.h>
62
63 /* clicon */
64 #include "clixon_log.h"
65 #include "clixon_err.h"
66 #include "clixon_string.h"
67 #include "clixon_queue.h"
68 #include "clixon_hash.h"
69 #include "clixon_handle.h"
70 #include "clixon_file.h"
71 #include "clixon_yang.h"
72 #include "clixon_xml.h"
73 #include "clixon_xml_io.h"
74 #include "clixon_xml_nsctx.h"
75 #include "clixon_xpath_ctx.h"
76 #include "clixon_xpath.h"
77 #include "clixon_options.h"
78 #include "clixon_data.h"
79 #include "clixon_yang_module.h"
80 #include "clixon_plugin.h"
81 #include "clixon_netconf_lib.h"
82 #include "clixon_xml_map.h"
83 #include "clixon_yang_parse_lib.h"
84
85 modstate_diff_t *
modstate_diff_new(void)86 modstate_diff_new(void)
87 {
88 modstate_diff_t *md;
89 if ((md = malloc(sizeof(modstate_diff_t))) == NULL){
90 clicon_err(OE_UNIX, errno, "malloc");
91 return NULL;
92 }
93 memset(md, 0, sizeof(modstate_diff_t));
94 return md;
95 }
96
97 int
modstate_diff_free(modstate_diff_t * md)98 modstate_diff_free(modstate_diff_t *md)
99 {
100 if (md == NULL)
101 return 0;
102 if (md->md_set_id)
103 free(md->md_set_id);
104 if (md->md_diff)
105 xml_free(md->md_diff);
106 free(md);
107 return 0;
108 }
109
110 /*! Init the Yang module library
111 *
112 * Load RFC7895 yang spec, module-set-id, etc.
113 * @param[in] h Clicon handle
114 */
115 int
yang_modules_init(clicon_handle h)116 yang_modules_init(clicon_handle h)
117 {
118 int retval = -1;
119 yang_stmt *yspec;
120
121 yspec = clicon_dbspec_yang(h);
122 if (!clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
123 goto ok;
124 /* Ensure module-set-id is set */
125 if (!clicon_option_exists(h, "CLICON_MODULE_SET_ID")){
126 clicon_err(OE_CFG, ENOENT, "CLICON_MODULE_SET_ID must be defined when CLICON_MODULE_LIBRARY_RFC7895 is enabled");
127 goto done;
128 }
129 /* Ensure revision exists is set */
130 if (yang_spec_parse_module(h, "ietf-yang-library", NULL, yspec)< 0)
131 goto done;
132 /* Find revision */
133 if (yang_modules_revision(h) == NULL){
134 clicon_err(OE_CFG, ENOENT, "Yang client library yang spec has no revision");
135 goto done;
136 }
137 ok:
138 retval = 0;
139 done:
140 return retval;
141 }
142
143 /*! Return RFC7895 revision (if parsed)
144 * @param[in] h Clicon handle
145 * @retval revision String (dont free)
146 * @retval NULL Error: RFC7895 not loaded or revision not found
147 */
148 char *
yang_modules_revision(clicon_handle h)149 yang_modules_revision(clicon_handle h)
150 {
151 yang_stmt *yspec;
152 yang_stmt *ymod;
153 yang_stmt *yrev;
154 char *revision = NULL;
155
156 yspec = clicon_dbspec_yang(h);
157 if ((ymod = yang_find(yspec, Y_MODULE, "ietf-yang-library")) != NULL ||
158 (ymod = yang_find(yspec, Y_SUBMODULE, "ietf-yang-library")) != NULL){
159 if ((yrev = yang_find(ymod, Y_REVISION, NULL)) != NULL){
160 revision = yang_argument_get(yrev);
161 }
162 }
163 return revision;
164 }
165
166 /*! Actually build the yang modules state XML tree
167 * @see RFC7895
168 */
169 static int
yms_build(clicon_handle h,yang_stmt * yspec,char * msid,int brief,cbuf * cb)170 yms_build(clicon_handle h,
171 yang_stmt *yspec,
172 char *msid,
173 int brief,
174 cbuf *cb)
175 {
176 int retval = -1;
177 yang_stmt *ylib = NULL; /* ietf-yang-library */
178 char *module = "ietf-yang-library";
179 yang_stmt *ys;
180 yang_stmt *yc;
181 yang_stmt *ymod; /* generic module */
182 yang_stmt *yns = NULL; /* namespace */
183
184 if ((ylib = yang_find(yspec, Y_MODULE, module)) == NULL &&
185 (ylib = yang_find(yspec, Y_SUBMODULE, module)) == NULL){
186 clicon_err(OE_YANG, 0, "%s not found", module);
187 goto done;
188 }
189 if ((yns = yang_find(ylib, Y_NAMESPACE, NULL)) == NULL){
190 clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
191 goto done;
192 }
193
194 cprintf(cb,"<modules-state xmlns=\"%s\">", yang_argument_get(yns));
195 cprintf(cb,"<module-set-id>%s</module-set-id>", msid);
196
197 ymod = NULL;
198 while ((ymod = yn_each(yspec, ymod)) != NULL) {
199 if (yang_keyword_get(ymod) != Y_MODULE &&
200 yang_keyword_get(ymod) != Y_SUBMODULE)
201 continue;
202 cprintf(cb,"<module>");
203 cprintf(cb,"<name>%s</name>", yang_argument_get(ymod));
204 if ((ys = yang_find(ymod, Y_REVISION, NULL)) != NULL)
205 cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
206 else{
207 /* RFC7895 1 If no (such) revision statement exists, the module's or
208 submodule's revision is the zero-length string. */
209 cprintf(cb,"<revision></revision>");
210 }
211 if ((ys = yang_find(ymod, Y_NAMESPACE, NULL)) != NULL)
212 cprintf(cb,"<namespace>%s</namespace>", yang_argument_get(ys));
213 else
214 cprintf(cb,"<namespace></namespace>");
215 /* This follows order in rfc 7895: feature, conformance-type,
216 submodules */
217 if (!brief){
218 yc = NULL;
219 while ((yc = yn_each(ymod, yc)) != NULL) {
220 switch(yang_keyword_get(yc)){
221 case Y_FEATURE:
222 if (yang_cv_get(yc) && cv_bool_get(yang_cv_get(yc)))
223 cprintf(cb,"<feature>%s</feature>", yang_argument_get(yc));
224 break;
225 default:
226 break;
227 }
228 }
229 cprintf(cb, "<conformance-type>implement</conformance-type>");
230 }
231 yc = NULL;
232 while ((yc = yn_each(ymod, yc)) != NULL) {
233 switch(yang_keyword_get(yc)){
234 case Y_SUBMODULE:
235 cprintf(cb,"<submodule>");
236 cprintf(cb,"<name>%s</name>", yang_argument_get(yc));
237 if ((ys = yang_find(yc, Y_REVISION, NULL)) != NULL)
238 cprintf(cb,"<revision>%s</revision>", yang_argument_get(ys));
239 else
240 cprintf(cb,"<revision></revision>");
241 cprintf(cb,"</submodule>");
242 break;
243 default:
244 break;
245 }
246 }
247 cprintf(cb,"</module>");
248 }
249 cprintf(cb,"</modules-state>");
250 retval = 0;
251 done:
252 return retval;
253 }
254
255 /*! Get modules state according to RFC 7895
256 * @param[in] h Clicon handle
257 * @param[in] yspec Yang spec
258 * @param[in] xpath XML Xpath
259 * @param[in] nsc XML Namespace context for xpath
260 * @param[in] brief Just name, revision and uri (no cache)
261 * @param[in,out] xret Existing XML tree, merge x into this
262 * @retval -1 Error (fatal)
263 * @retval 0 Statedata callback failed
264 * @retval 1 OK
265 * @notes NYI: schema, deviation
266 x +--ro modules-state
267 x +--ro module-set-id string
268 x +--ro module* [name revision]
269 x +--ro name yang:yang-identifier
270 x +--ro revision union
271 +--ro schema? inet:uri
272 x +--ro namespace inet:uri
273 +--ro feature* yang:yang-identifier
274 +--ro deviation* [name revision]
275 | +--ro name yang:yang-identifier
276 | +--ro revision union
277 +--ro conformance-type enumeration
278 +--ro submodule* [name revision]
279 +--ro name yang:yang-identifier
280 +--ro revision union
281 +--ro schema? inet:uri
282 * @see netconf_hello_server
283 */
284 int
yang_modules_state_get(clicon_handle h,yang_stmt * yspec,char * xpath,cvec * nsc,int brief,cxobj ** xret)285 yang_modules_state_get(clicon_handle h,
286 yang_stmt *yspec,
287 char *xpath,
288 cvec *nsc,
289 int brief,
290 cxobj **xret)
291 {
292 int retval = -1;
293 cxobj *x = NULL; /* Top tree, some juggling w top symbol */
294 char *msid; /* modules-set-id */
295 cxobj *xc; /* original cache */
296 cbuf *cb = NULL;
297 int ret;
298 cxobj **xvec = NULL;
299 size_t xlen;
300 int i;
301
302 msid = clicon_option_str(h, "CLICON_MODULE_SET_ID");
303 if ((xc = clicon_modst_cache_get(h, brief)) != NULL){
304 cxobj *xw; /* tmp top wrap object */
305 /* xc is here: <modules-state>...
306 * need to wrap it for xpath: <top><modules-state> */
307 /* xc is also original tree, need to copy it */
308 if ((xw = xml_wrap(xc, "top")) == NULL)
309 goto done;
310 if (xpath_first(xw, nsc, "%s", xpath)){
311 if ((x = xml_dup(xc)) == NULL) /* Make copy and use below */
312 goto done;
313 }
314 if (xml_rootchild_node(xw, xc) < 0) /* Unwrap x / free xw */
315 goto done;
316 }
317 else { /* No cache -> build the tree */
318 if ((cb = cbuf_new()) == NULL){
319 clicon_err(OE_UNIX, 0, "clicon buffer");
320 goto done;
321 }
322 /* Build a cb string: <modules-state>... */
323 if (yms_build(h, yspec, msid, brief, cb) < 0)
324 goto done;
325 /* Parse cb, x is on the form: <top><modules-state>...
326 * Note, list is not sorted since it is state (should not be)
327 */
328 if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x, NULL) < 0){
329 if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
330 goto done;
331 goto fail;
332 }
333 if (xml_rootchild(x, 0, &x) < 0)
334 goto done;
335 /* x is now: <modules-state>... */
336 if (clicon_modst_cache_set(h, brief, x) < 0) /* move to fn above? */
337 goto done;
338 }
339 if (x){ /* x is here a copy (This code is ugly and I think wrong) */
340 /* Wrap x (again) with new top-level node "top" which xpath wants */
341 if ((x = xml_wrap(x, "top")) < 0)
342 goto done;
343 /* extract xpath part of module-state tree */
344 if (xpath_vec(x, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
345 goto done;
346 if (xvec != NULL){
347 for (i=0; i<xlen; i++)
348 xml_flag_set(xvec[i], XML_FLAG_MARK);
349 }
350 /* Remove everything that is not marked */
351 if (xml_tree_prune_flagged_sub(x, XML_FLAG_MARK, 1, NULL) < 0)
352 goto done;
353
354 if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
355 goto done;
356 if (ret == 0)
357 goto fail;
358 }
359 retval = 1;
360 done:
361 clicon_debug(1, "%s %d", __FUNCTION__, retval);
362 if (xvec)
363 free(xvec);
364 if (cb)
365 cbuf_free(cb);
366 if (x)
367 xml_free(x);
368 return retval;
369 fail:
370 retval = 0;
371 goto done;
372 }
373
374 /*! For single module state with namespace, get revisions and send upgrade callbacks
375 * @param[in] h Clicon handle
376 * @param[in] xt Top-level XML tree to be updated (includes other ns as well)
377 * @param[in] xd XML module state diff (for one yang module)
378 * @param[in] xvec Help vector where to store XML child nodes (??)
379 * @param[in] xlen Length of xvec
380 * @param[in] ns0 Namespace of module state we are looking for
381 * @param[in] op add,del, or mod
382 * @param[out] cbret Netconf error message if invalid
383 * @retval 1 OK
384 * @retval 0 Validation failed (cbret set)
385 * @retval -1 Error
386 */
387 static int
mod_ns_upgrade(clicon_handle h,cxobj * xt,cxobj * xmod,char * ns,cbuf * cbret)388 mod_ns_upgrade(clicon_handle h,
389 cxobj *xt,
390 cxobj *xmod,
391 char *ns,
392 cbuf *cbret)
393 {
394 int retval = -1;
395 char *b; /* string body */
396 yang_stmt *ymod;
397 yang_stmt *yrev;
398 uint32_t from = 0;
399 uint32_t to = 0;
400 int ret;
401 yang_stmt *yspec;
402
403 /* If modified or removed get from revision from file */
404 if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_DEL)) != 0x0){
405 if ((b = xml_find_body(xmod, "revision")) != NULL) /* Module revision */
406 if (ys_parse_date_arg(b, &from) < 0)
407 goto done;
408 }
409 /* If modified or added get to revision from system */
410 if (xml_flag(xmod, (XML_FLAG_CHANGE|XML_FLAG_ADD)) != 0x0){
411 yspec = clicon_dbspec_yang(h);
412 if ((ymod = yang_find_module_by_namespace(yspec, ns)) == NULL)
413 goto fail;
414 if ((yrev = yang_find(ymod, Y_REVISION, NULL)) == NULL){
415 retval = 1;
416 goto done;
417 }
418 if (ys_parse_date_arg(yang_argument_get(yrev), &to) < 0)
419 goto done;
420 }
421 if ((ret = upgrade_callback_call(h, xt, ns,
422 xml_flag(xmod, (XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE)),
423 from, to, cbret)) < 0)
424 goto done;
425 if (ret == 0) /* XXX ignore and continue? */
426 goto fail;
427 retval = 1;
428 done:
429 return retval;
430 fail:
431 retval = 0;
432 goto done;
433 }
434
435 /*! Upgrade XML
436 * @param[in] h Clicon handle
437 * @param[in] xt XML tree (to upgrade)
438 * @param[in] msd Modules-state differences of xt
439 * @param[out] cbret Netconf error message if invalid
440 * @retval 1 OK
441 * @retval 0 Validation failed (cbret set)
442 * @retval -1 Error
443 */
444 int
clixon_module_upgrade(clicon_handle h,cxobj * xt,modstate_diff_t * msd,cbuf * cbret)445 clixon_module_upgrade(clicon_handle h,
446 cxobj *xt,
447 modstate_diff_t *msd,
448 cbuf *cbret)
449 {
450 int retval = -1;
451 char *ns; /* Namespace */
452 cxobj *xmod; /* XML module state diff */
453 int ret;
454
455 if (msd == NULL){
456 clicon_err(OE_CFG, EINVAL, "No modstate");
457 goto done;
458 }
459 if (msd->md_status == 0) /* No modstate in startup */
460 goto ok;
461 /* Iterate through xml modified module state */
462 xmod = NULL;
463 while ((xmod = xml_child_each(msd->md_diff, xmod, CX_ELMNT)) != NULL) {
464 /* Extract namespace */
465 if ((ns = xml_find_body(xmod, "namespace")) == NULL)
466 goto done;
467 /* Extract revisions and make callbacks */
468 if ((ret = mod_ns_upgrade(h, xt, xmod, ns, cbret)) < 0)
469 goto done;
470 if (ret == 0)
471 goto fail;
472 }
473 ok:
474 retval = 1;
475 done:
476 return retval;
477 fail:
478 retval = 0;
479 goto done;
480 }
481
482 /*! Given a yang statement and a prefix, return yang module to that relative prefix
483 * Note, not the other module but the proxy import statement only
484 * @param[in] ys A yang statement
485 * @param[in] prefix prefix
486 * @retval ymod Yang module statement if found
487 * @retval NULL not found
488 * @note Prefixes are relative to the module they are defined
489 * @see yang_find_module_by_name
490 * @see yang_find_module_by_namespace
491 */
492 yang_stmt *
yang_find_module_by_prefix(yang_stmt * ys,char * prefix)493 yang_find_module_by_prefix(yang_stmt *ys,
494 char *prefix)
495 {
496 yang_stmt *yimport;
497 yang_stmt *yprefix;
498 yang_stmt *my_ymod;
499 yang_stmt *ymod = NULL;
500 yang_stmt *yspec;
501 char *myprefix;
502
503 if ((yspec = ys_spec(ys)) == NULL){
504 clicon_err(OE_YANG, 0, "My yang spec not found");
505 goto done;
506 }
507 /* First try own module */
508 if ((my_ymod = ys_module(ys)) == NULL){
509 clicon_err(OE_YANG, 0, "My yang module not found");
510 goto done;
511 }
512 myprefix = yang_find_myprefix(ys);
513 if (myprefix && strcmp(myprefix, prefix) == 0){
514 ymod = my_ymod;
515 goto done;
516 }
517 /* If no match, try imported modules */
518 yimport = NULL;
519 while ((yimport = yn_each(my_ymod, yimport)) != NULL) {
520 if (yang_keyword_get(yimport) != Y_IMPORT)
521 continue;
522 if ((yprefix = yang_find(yimport, Y_PREFIX, NULL)) != NULL &&
523 strcmp(yang_argument_get(yprefix), prefix) == 0){
524 break;
525 }
526 }
527 if (yimport){
528 if ((ymod = yang_find(yspec, Y_MODULE, yang_argument_get(yimport))) == NULL){
529 clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s",
530 prefix);
531 yimport = NULL;
532 goto done; /* unresolved */
533 }
534 }
535 done:
536 return ymod;
537 }
538
539 /* Get module from its own prefix
540 * This is really not a valid usecase, a kludge for the identityref derived
541 * list workaround (IDENTITYREF_KLUDGE)
542 * Actually, for canonical prefixes it is!
543 */
544 yang_stmt *
yang_find_module_by_prefix_yspec(yang_stmt * yspec,char * prefix)545 yang_find_module_by_prefix_yspec(yang_stmt *yspec,
546 char *prefix)
547 {
548 yang_stmt *ymod = NULL;
549 yang_stmt *yprefix;
550
551 while ((ymod = yn_each(yspec, ymod)) != NULL)
552 if (yang_keyword_get(ymod) == Y_MODULE &&
553 (yprefix = yang_find(ymod, Y_PREFIX, NULL)) != NULL &&
554 strcmp(yang_argument_get(yprefix), prefix) == 0)
555 return ymod;
556 return NULL;
557 }
558
559 /*! Given a yang spec and a namespace, return yang module
560 *
561 * @param[in] yspec A yang specification
562 * @param[in] ns namespace
563 * @retval ymod Yang module statement if found
564 * @retval NULL not found
565 * @see yang_find_module_by_name
566 * @see yang_find_module_by_prefix module-specific prefix
567 */
568 yang_stmt *
yang_find_module_by_namespace(yang_stmt * yspec,char * ns)569 yang_find_module_by_namespace(yang_stmt *yspec,
570 char *ns)
571 {
572 yang_stmt *ymod = NULL;
573
574 if (ns == NULL)
575 goto done;
576 while ((ymod = yn_each(yspec, ymod)) != NULL) {
577 if (yang_find(ymod, Y_NAMESPACE, ns) != NULL)
578 break;
579 }
580 done:
581 return ymod;
582 }
583
584 /*! Given a yang spec and a module name, return yang module or submodule
585 *
586 * @param[in] yspec A yang specification
587 * @param[in] name Name of module
588 * @retval ymod Yang module statement if found
589 * @retval NULL not found
590 * @see yang_find_module_by_namespace
591 * @see yang_find_module_by_prefix module-specific prefix
592 */
593 yang_stmt *
yang_find_module_by_name(yang_stmt * yspec,char * name)594 yang_find_module_by_name(yang_stmt *yspec,
595 char *name)
596 {
597 yang_stmt *ymod = NULL;
598
599 while ((ymod = yn_each(yspec, ymod)) != NULL)
600 if ((yang_keyword_get(ymod) == Y_MODULE || yang_keyword_get(ymod) == Y_SUBMODULE) &&
601 strcmp(yang_argument_get(ymod), name)==0)
602 return ymod;
603 return NULL;
604 }
605