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