1 /*
2  *
3   ***** BEGIN LICENSE BLOCK *****
4 
5   Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6   Copyright (C) 2017-2019 Olof Hagsand
7   Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8 
9   This file is part of CLIXON.
10 
11   Licensed under the Apache License, Version 2.0 (the "License");
12   you may not use this file except in compliance with the License.
13   You may obtain a copy of the License at
14 
15     http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22 
23   Alternatively, the contents of this file may be used under the terms of
24   the GNU General Public License Version 3 or later (the "GPL"),
25   in which case the provisions of the GPL are applicable instead
26   of those above. If you wish to allow use of your version of this file only
27   under the terms of the GPL, and not to allow others to
28   use your version of this file under the terms of Apache License version 2,
29   indicate your decision by deleting the provisions above and replace them with
30   the  notice and other provisions required by the GPL. If you do not delete
31   the provisions above, a recipient may use your version of this file under
32   the terms of any one of the Apache License version 2 or the GPL.
33 
34   ***** END LICENSE BLOCK *****
35 
36  * XML support functions.
37  * @see     https://www.w3.org/TR/2009/REC-xml-names-20091208
38  * An xml namespace context is a cligen variable vector containing a list of
39  * <prefix,namespace> pairs.
40  * It is encoded in a cvv as a list of string values, where the c name is the
41  * prefix and the string values are the namespace URI.
42  * The default namespace is decoded as having the name NULL
43  */
44 
45 #ifdef HAVE_CONFIG_H
46 #include "clixon_config.h" /* generated by config & autoconf */
47 #endif
48 
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <unistd.h>
52 #include <errno.h>
53 #include <string.h>
54 #include <limits.h>
55 #include <stdint.h>
56 
57 /* cligen */
58 #include <cligen/cligen.h>
59 
60 /* clixon */
61 #include "clixon_err.h"
62 #include "clixon_string.h"
63 #include "clixon_queue.h"
64 #include "clixon_hash.h"
65 #include "clixon_handle.h"
66 #include "clixon_log.h"
67 #include "clixon_options.h"
68 #include "clixon_yang.h"
69 #include "clixon_xml.h"
70 #include "clixon_yang_module.h"
71 #include "clixon_xml_nsctx.h"
72 
73 /* Undefine if you want to ensure strict namespace assignment on all netconf
74  * and XML statements according to the standard RFC 6241.
75  * If defined, top-level rpc calls need not have namespaces (eg using xmlns=<ns>)
76  * since the default NETCONF namespace will be assumed. (This is not standard).
77  * See rfc6241 3.1: urn:ietf:params:xml:ns:netconf:base:1.0.
78  */
79 static int _USE_NAMESPACE_NETCONF_DEFAULT = 0;
80 
81 /*! Set if use internal default namespace mechanism or not
82  *
83  * This function shouldnt really be here, it sets a local variable from the value of the
84  * option CLICON_NAMESPACE_NETCONF_DEFAULT, but the code where it is used is deep in the call
85  * stack and cannot get the clicon handle currently.
86  * @param[in] h  Clicon handle
87  */
88 int
xml_nsctx_namespace_netconf_default(clicon_handle h)89 xml_nsctx_namespace_netconf_default(clicon_handle h)
90 {
91     _USE_NAMESPACE_NETCONF_DEFAULT = clicon_option_bool(h, "CLICON_NAMESPACE_NETCONF_DEFAULT");
92     return 0;
93 }
94 
95 /*! Create and initialize XML namespace context
96  * @param[in] prefix    Namespace prefix, or NULL for default
97  * @param[in] ns	    Set this namespace. If NULL create empty nsctx
98  * @retval    nsc       Return namespace context in form of a cvec
99  * @retval    NULL      Error
100  * @code
101  * cvec *nsc = NULL;
102  * if ((nsc = xml_nsctx_init(NULL, "urn:example:example")) == NULL)
103  *   err;
104  * ...
105  * xml_nsctx_free(nsc);
106  * @endcode
107  * @see xml_nsctx_node  Use namespace context of an existing XML node
108  * @see xml_nsctx_free  Free the reutned handle
109  */
110 cvec *
xml_nsctx_init(char * prefix,char * ns)111 xml_nsctx_init(char  *prefix,
112 	       char  *ns)
113 {
114     cvec *cvv = NULL;
115 
116     if ((cvv = cvec_new(0)) == NULL){
117 	clicon_err(OE_XML, errno, "cvec_new");
118 	goto done;
119     }
120     if (ns && xml_nsctx_add(cvv, prefix, ns) < 0)
121 	goto done;
122  done:
123     return cvv;
124 }
125 
126 /*! Free XML namespace context
127  * @param[in] prefix    Namespace prefix, or NULL for default
128  * @param[in] namespace Cached namespace to set (assume non-null?)
129  * @retval    nsc       Return namespace context in form of a cvec
130  * @retval    NULL      Error
131  */
132 int
xml_nsctx_free(cvec * nsc)133 xml_nsctx_free(cvec *nsc)
134 {
135     cvec *cvv = (cvec*)nsc;
136 
137     if (cvv)
138 	cvec_free(cvv);
139     return 0;
140 }
141 
142 /*! Get namespace given prefix (or NULL for default) from namespace context
143  * @param[in] cvv    Namespace context
144  * @param[in] prefix Namespace prefix, or NULL for default
145  * @retval    ns     Cached namespace
146  * @retval    NULL   No namespace found (not cached or not found)
147  */
148 char*
xml_nsctx_get(cvec * cvv,char * prefix)149 xml_nsctx_get(cvec *cvv,
150 	      char *prefix)
151 {
152     cg_var *cv;
153 
154     if ((cv = cvec_find(cvv, prefix)) != NULL)
155 	return cv_string_get(cv);
156     return NULL;
157 }
158 
159 /*! Reverse get prefix given namespace
160  * @param[in]  cvv    Namespace context
161  * @param[in]  ns     Namespace
162  * @param[out] prefix Prefix (direct pointer)
163  * @retval     0      No prefix found
164  * @retval     1      Prefix found
165  * @note NULL is a valid prefix (default)
166  */
167 int
xml_nsctx_get_prefix(cvec * cvv,char * ns,char ** prefix)168 xml_nsctx_get_prefix(cvec  *cvv,
169 		     char  *ns,
170 		     char **prefix)
171 {
172     cg_var *cv = NULL;
173     char   *ns0 = NULL;
174 
175     while ((cv = cvec_each(cvv, cv)) != NULL){
176 	if ((ns0 = cv_string_get(cv)) != NULL &&
177 	    strcmp(ns0, ns) == 0){
178 	    if (prefix)
179 		*prefix = cv_name_get(cv); /* can be NULL */
180 	    return 1;
181 	}
182     }
183     if (prefix)
184 	*prefix = NULL;
185     return 0;
186 }
187 
188 /*! Set or replace namespace in namespace context
189  * @param[in] cvv       Namespace context
190  * @param[in] prefix    Namespace prefix, or NULL for default
191  * @param[in] ns	    Cached namespace to set (assume non-null?)
192  * @retval    0         OK
193  * @retval   -1         Error
194  */
195 int
xml_nsctx_add(cvec * cvv,char * prefix,char * ns)196 xml_nsctx_add(cvec  *cvv,
197 	      char  *prefix,
198 	      char  *ns)
199 {
200     int     retval = -1;
201     cg_var *cv;
202 
203     if ((cv = cvec_find(cvv, prefix)) != NULL) /* found, replace that */
204 	cv_string_set(cv, ns);
205     else /* cvec exists, but not prefix */
206 	cvec_add_string(cvv, prefix, ns);
207     retval = 0;
208     // done:
209     return retval;
210 }
211 
212 static int
xml_nsctx_node1(cxobj * xn,cvec * nsc)213 xml_nsctx_node1(cxobj *xn,
214 		cvec   *nsc)
215 {
216     int    retval = -1;
217     cxobj *xa = NULL;
218     char  *pf;  /* prefix */
219     char  *nm;  /* name */
220     char  *val; /* value */
221     cxobj *xp;  /* parent */
222 
223     /* xmlns:t="<ns1>" prefix:xmlns, name:t
224      * xmlns="<ns2>"   prefix:NULL   name:xmlns
225      */
226     while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL){
227 	pf = xml_prefix(xa);
228 	nm = xml_name(xa);
229 	if (pf == NULL){
230 	    if (strcmp(nm, "xmlns")==0 && /* set default namespace context */
231 		xml_nsctx_get(nsc, NULL) == NULL){
232 		val = xml_value(xa);
233 		if (xml_nsctx_add(nsc, NULL, val) < 0)
234 		    goto done;
235 	    }
236 	}
237 	else
238 	    if (strcmp(pf, "xmlns")==0 && /* set prefixed namespace context */
239 		xml_nsctx_get(nsc, nm) == NULL){
240 		val = xml_value(xa);
241 		if (xml_nsctx_add(nsc, nm, val) < 0)
242 		    goto done;
243 	    }
244     }
245     if ((xp = xml_parent(xn)) == NULL){
246 	if (_USE_NAMESPACE_NETCONF_DEFAULT){
247 	    /* If not default namespace defined, use the base netconf ns as default */
248 	    if (xml_nsctx_get(nsc, NULL) == NULL)
249 		if (xml_nsctx_add(nsc, NULL, NETCONF_BASE_NAMESPACE) < 0)
250 		    goto done;
251 	}
252     }
253     else
254 	if (xml_nsctx_node1(xp, nsc) < 0)
255 	    goto done;
256     retval = 0;
257  done:
258     return retval;
259 }
260 
261 /*! Create and initialize XML namespace from XML node context
262  * Fully explore all prefix:namespace pairs from context of one node
263  * @param[in]  xn     XML node
264  * @param[out] ncp    XML namespace context
265  * @retval     0      OK
266  * @retval     -1     Error
267  * @code
268  * cxobj *x; // must initialize
269  * cvec *nsc = NULL;
270  * if (xml_nsctx_node(x, &nsc) < 0)
271  *   err
272  * ...
273  * xml_nsctx_free(nsc)
274  * @endcode
275  * @see xml_nsctx_init
276  * @see xml_nsctx_free  Free the returned handle
277  */
278 int
xml_nsctx_node(cxobj * xn,cvec ** ncp)279 xml_nsctx_node(cxobj *xn,
280 	       cvec **ncp)
281 {
282     int   retval = -1;
283     cvec *nc = NULL;
284 
285     if ((nc = cvec_new(0)) == NULL){
286 	clicon_err(OE_XML, errno, "cvec_new");
287 	goto done;
288     }
289     if (xml_nsctx_node1(xn, nc) < 0)
290 	goto done;
291     *ncp = nc;
292     retval = 0;
293  done:
294     return retval;
295 }
296 
297 /*! Create and initialize XML namespace context from Yang node
298  * Primary use is Yang path statements, eg leafrefs and others
299  * Fully explore all prefix:namespace pairs from context of one node
300  * @param[in]  yn     Yang statement in module tree (or module itself)
301  * @param[out] ncp    XML namespace context
302  * @retval     0      OK
303  * @retval     -1     Error
304  * @code
305  * yang_stmt *y; // must initialize
306  * cvec *nsc = NULL;
307  * if (xml_nsctx_yang(y, &nsc) < 0)
308  *   err
309  * ...
310  * xml_nsctx_free(nsc)
311  * @endcode
312  * @see RFC7950 Sections 6.4.1 (and 9.9.2?)
313  * @note Assume yn is in a yang structure (eg has parents and belongs to a (sub)module)
314  */
315 int
xml_nsctx_yang(yang_stmt * yn,cvec ** ncp)316 xml_nsctx_yang(yang_stmt *yn,
317 	       cvec     **ncp)
318 {
319     int        retval = -1;
320     cvec      *nc = NULL;
321     yang_stmt *yspec;
322     yang_stmt *ymod;  /* yang main module/submodule node */
323     yang_stmt *yp;    /* yang prefix node */
324     yang_stmt *ym;    /* yang imported module */
325     yang_stmt *yns;   /* yang namespace */
326     yang_stmt *y;
327     char      *name;
328     char      *namespace;
329     char      *prefix;
330     char      *mynamespace;
331     char      *myprefix;
332 
333     if ((nc = cvec_new(0)) == NULL){
334 	clicon_err(OE_XML, errno, "cvec_new");
335 	goto done;
336     }
337     if ((myprefix = yang_find_myprefix(yn)) == NULL){
338 	clicon_err(OE_YANG, ENOENT, "My yang prefix not found");
339 	goto done;
340     }
341     if ((mynamespace = yang_find_mynamespace(yn)) == NULL){
342 	clicon_err(OE_YANG, ENOENT, "My yang namespace not found");
343 	goto done;
344     }
345     /* Add my prefix and default namespace (from real module) */
346     if (xml_nsctx_add(nc, NULL, mynamespace) < 0)
347 	goto done;
348     if (xml_nsctx_add(nc, myprefix, mynamespace) < 0)
349 	goto done;
350     /* Find top-most module or sub-module and get prefixes from that */
351     if ((ymod = ys_module(yn)) == NULL){
352 	clicon_err(OE_YANG, ENOENT, "My yang module not found");
353 	goto done;
354     }
355     yspec = yang_parent_get(ymod); /* Assume yspec exists */
356 
357     /* Iterate over module and register all import prefixes
358      */
359     y = NULL;
360     while ((y = yn_each(ymod, y)) != NULL) {
361 	if (yang_keyword_get(y) == Y_IMPORT){
362 	    if ((name = yang_argument_get(y)) == NULL)
363 		continue; /* Just skip - shouldnt happen) */
364 	    if ((yp = yang_find(y, Y_PREFIX, NULL)) == NULL)
365 		continue;
366 	    if ((prefix = yang_argument_get(yp)) == NULL)
367 		continue;
368 	    if ((ym = yang_find(yspec, Y_MODULE, name)) == NULL)
369 		continue;
370 	    if ((yns = yang_find(ym, Y_NAMESPACE, NULL)) == NULL)
371 		continue;
372 	    if ((namespace = yang_argument_get(yns)) == NULL)
373 		continue;
374 	    if (xml_nsctx_add(nc, prefix, namespace) < 0)
375 		goto done;
376 	}
377     }
378     *ncp = nc;
379     retval = 0;
380  done:
381     return retval;
382 }
383 
384 /*! Create and initialize XML namespace context from Yang spec
385  *
386  * That is, create a "canonical" XML namespace mapping from all loaded yang
387  * modules which are children of the yang specification.
388  * Also add netconf base namespace: nc , urn:ietf:params:xml:ns:netconf:base:1.0
389  * Fully explore all prefix:namespace pairs of all yang modules
390  * @param[in]  yspec  Yang spec
391  * @param[out] ncp    XML namespace context
392  * @retval     0      OK
393  * @retval    -1      Error
394  * @code
395  *  cvec      *nsc = NULL;
396  *  yang_stmt *yspec = clicon_dbspec_yang(h);
397  *  if (xml_nsctx_yangspec(yspec, &nsc) < 0)
398  *	goto done;
399  *  ...
400  *  cvec_free(nsc);
401  * @endcode
402  */
403 int
xml_nsctx_yangspec(yang_stmt * yspec,cvec ** ncp)404 xml_nsctx_yangspec(yang_stmt *yspec,
405 		   cvec     **ncp)
406 {
407     int        retval = -1;
408     cvec      *nc = NULL;
409     yang_stmt *ymod = NULL;
410     yang_stmt *yprefix;
411     yang_stmt *ynamespace;
412 
413     if ((nc = cvec_new(0)) == NULL){
414 	clicon_err(OE_XML, errno, "cvec_new");
415 	goto done;
416     }
417     ymod = NULL;
418     while ((ymod = yn_each(yspec, ymod)) != NULL){
419 	if (yang_keyword_get(ymod) != Y_MODULE)
420 	    continue;
421 	if ((yprefix = yang_find(ymod, Y_PREFIX, NULL)) == NULL)
422 	    continue;
423 	if ((ynamespace = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL)
424 	    continue;
425 	if (xml_nsctx_add(nc, yang_argument_get(yprefix), yang_argument_get(ynamespace)) < 0)
426 	    goto done;
427     }
428     /* Add base netconf namespace as default and "nc" prefix */
429     if (xml_nsctx_add(nc,  NULL, NETCONF_BASE_NAMESPACE) < 0)
430 	goto done;
431     if (xml_nsctx_add(nc,  NETCONF_BASE_PREFIX, NETCONF_BASE_NAMESPACE) < 0)
432 	goto done;
433     *ncp = nc;
434     retval = 0;
435  done:
436     return retval;
437 }
438 
439 /*! Given an xml tree return URI namespace recursively : default or localname given
440  *
441  * Given an XML tree and a prefix (or NULL) return URI namespace.
442  * @param[in]  x          XML tree
443  * @param[in]  prefix     prefix/ns localname. If NULL then return default.
444  * @param[out] namespace  URI namespace (or NULL). Note pointer into xml tree
445  * @retval     0          OK
446  * @retval    -1          Error
447  * @code
448  *   if (xml2ns(xt, NULL, &namespace) < 0)
449  *      err;
450  * @endcode
451  * @see xmlns_set cache is set
452  * @note, this function uses a cache.
453  */
454 int
xml2ns(cxobj * x,char * prefix,char ** namespace)455 xml2ns(cxobj *x,
456        char  *prefix,
457        char **namespace)
458 {
459     int    retval = -1;
460     char  *ns = NULL;
461     cxobj *xp;
462 
463     if ((ns = nscache_get(x, prefix)) != NULL)
464 	goto ok;
465     if (prefix != NULL) /* xmlns:<prefix>="<uri>" */
466 	ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
467     else{                /* xmlns="<uri>" */
468     	ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
469     }
470     /* namespace not found, try parent */
471     if (ns == NULL){
472 	if ((xp = xml_parent(x)) != NULL){
473 	    if (xml2ns(xp, prefix, &ns) < 0)
474 		goto done;
475 	}
476 	/* If no parent, return default namespace if defined */
477 	else if (_USE_NAMESPACE_NETCONF_DEFAULT){
478 	    if (prefix == NULL)
479 		ns = NETCONF_BASE_NAMESPACE;
480 	    else
481 		ns = NULL;
482 	}
483     }
484     /* Set default namespace cache (since code is at this point,
485      * no cache was found
486      * If not, this is devastating when populating deep yang structures
487      */
488     if (ns &&
489 	xml_child_nr(x) > 1 && 	/* Dont set cache if few children: if 1 child typically a body */
490 	nscache_set(x, prefix, ns) < 0)
491 	goto done;
492  ok:
493     if (namespace)
494 	*namespace = ns;
495     retval = 0;
496  done:
497     return retval;
498 }
499 
500 /*! Recursively check prefix / namespaces (and populate ns cache)
501  * @retval     1          OK
502  * @retval     0          (Some) prefix not found
503  * @retval    -1          Error
504  */
505 int
xml2ns_recurse(cxobj * xt)506 xml2ns_recurse(cxobj *xt)
507 {
508     int    retval = -1;
509     cxobj *x;
510     char  *prefix;
511     char  *namespace;
512 
513     x = NULL;
514     while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
515 	if ((prefix = xml_prefix(x)) != NULL){
516 	    namespace = NULL;
517 	    if (xml2ns(x, prefix, &namespace) < 0)
518 		goto done;
519 	    if (namespace == NULL){
520 		clicon_err(OE_XML, ENOENT, "No namespace associated with %s:%s", prefix, xml_name(x));
521 		goto done;
522 	    }
523 	}
524 	if (xml2ns_recurse(x) < 0)
525 	    goto done;
526     }
527     retval = 0;
528  done:
529     return retval;
530 }
531 
532 /*! Add a namespace attribute to an XML node, either default or specific prefix
533  * @param[in]  x          XML tree
534  * @param[in]  prefix     prefix/ns localname. If NULL then set default xmlns
535  * @param[in]  ns         URI namespace (or NULL). Will be copied
536  * @retval     0          OK
537  * @retval    -1          Error
538  * @see xml2ns
539  */
540 int
xmlns_set(cxobj * x,char * prefix,char * ns)541 xmlns_set(cxobj *x,
542 	  char  *prefix,
543 	  char  *ns)
544 {
545     int    retval = -1;
546     cxobj *xa;
547 
548     if (prefix != NULL){ /* xmlns:<prefix>="<uri>" */
549 	if ((xa = xml_new(prefix, x, CX_ATTR)) == NULL)
550 	    goto done;
551 	if (xml_prefix_set(xa, "xmlns") < 0)
552 	    goto done;
553     }
554     else{                /* xmlns="<uri>" */
555 	if ((xa = xml_new("xmlns", x, CX_ATTR)) == NULL)
556 	    goto done;
557     }
558     if (xml_value_set(xa, ns) < 0)
559 	goto done;
560     /* (re)set namespace cache (as used in xml2ns) */
561     if (ns && nscache_set(x, prefix, ns) < 0)
562 	goto done;
563     retval = 0;
564  done:
565     return retval;
566 }
567 
568 /*! Get prefix of given namespace recursively
569  * @param[in]  xn        XML node
570  * @param[in]  namespace Namespace
571  * @param[out] prefixp   Pointer to prefix if found
572  * @retval    -1         Error
573  * @retval     0         No namespace found
574  * @retval     1         Namespace found, prefix returned in prefixp
575  */
576 int
xml2prefix(cxobj * xn,char * namespace,char ** prefixp)577 xml2prefix(cxobj *xn,
578 	   char  *namespace,
579 	   char **prefixp)
580 {
581     int    retval = -1;
582     cxobj *xa = NULL;
583     cxobj *xp;
584     char  *prefix = NULL;
585     char  *xaprefix;
586     int    ret;
587 
588     if (nscache_get_prefix(xn, namespace, &prefix) == 1) /* found */
589 	goto found;
590     xa = NULL;
591     while ((xa = xml_child_each(xn, xa, CX_ATTR)) != NULL) {
592 	/* xmlns=namespace */
593 	if (strcmp("xmlns", xml_name(xa)) == 0){
594 	    if (strcmp(xml_value(xa), namespace) == 0){
595 		if (nscache_set(xn, NULL, namespace) < 0)
596 		    goto done;
597 		prefix = NULL; /* Maybe should set all caches in ns:s children? */
598 		goto found;
599 	    }
600 	}
601 	/* xmlns:prefix=namespace */
602 	else if ((xaprefix=xml_prefix(xa)) != NULL &&
603 		 strcmp("xmlns", xaprefix) == 0){
604 	    if (strcmp(xml_value(xa), namespace) == 0){
605 		prefix = xml_name(xa);
606 		if (nscache_set(xn, prefix, namespace) < 0)
607 		    goto done;
608 		goto found;
609 	    }
610 	}
611     }
612     if ((xp = xml_parent(xn)) != NULL){
613 	if ((ret = xml2prefix(xp, namespace, &prefix)) < 0)
614 	    goto done;
615 	if (ret == 1){
616 	    if (nscache_set(xn, prefix, namespace) < 0)
617 		goto done;
618 	    goto found;
619 	}
620     }
621     retval = 0;
622  done:
623     return retval;
624  found:
625     if (prefixp)
626 	*prefixp = prefix;
627     retval = 1;
628     goto done;
629 }
630 
631