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  * This file has code for several variants of paths in cxobj trees:
36  * - api-path as defined by RESTCONF
37  * - instance-identifier as defined by YANG
38  * - clixon-path is an internal format which both ^ use as internal representation
39  *
40  * 1. Instance-identifier
41  * "Instance-identifier" is a subset of XML Xpaths and defined in Yang, used in NACM for example.
42  *  and defined in RF7950 Sections 9.13 and 14.
43  *  To note: prefixes depend on the XML context in which the value occurs,
44  *  In RFC8341 node-instance-identifiers are defined as:
45  *   All the same rules as an instance-identifier apply, except that predicates for keys are optional.
46  *   If a key predicate is missing, then the node-instance-identifier represents all possible server
47  *   instances for that key.
48  *
49  * 2. Api-path
50  * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
51  * BNF:
52  *  <api-path>       := <root> ("/" (<api-identifier> | <list-instance>))*
53  *  <root>           := <string> # See note 1 below
54  *  <api-identifier> := [<module-name> ":"] <identifier>
55  *  <module-name>    := <identifier>
56  *  <list-instance>  := <api-identifier> "=" key-value *("," key-value)
57  *  <key-value>      := <string>
58  *  <string>         := <an unquoted string>
59  *  <identifier>     := (<ALPHA> | "_") (<ALPHA> | <DIGIT> | "_" | "-" | ".")
60  *
61  * @note 1. <root> is the RESTCONF root resource (Sec 3.3) omitted in all calls below, it is
62  *          assumed to be stripped from api-path before calling these functions.
63  * @note 2. characters in a key value string are constrained, and some characters need to be
64  *          percent-encoded,
65  */
66 #ifdef HAVE_CONFIG_H
67 #include "clixon_config.h" /* generated by config & autoconf */
68 #endif
69 
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <unistd.h>
73 #include <errno.h>
74 #include <ctype.h>
75 #include <string.h>
76 #include <syslog.h>
77 #include <fcntl.h>
78 #include <assert.h>
79 #include <arpa/inet.h>
80 #include <sys/param.h>
81 #include <netinet/in.h>
82 
83 /* cligen */
84 #include <cligen/cligen.h>
85 
86 /* clixon */
87 #include "clixon_string.h"
88 #include "clixon_queue.h"
89 #include "clixon_hash.h"
90 #include "clixon_handle.h"
91 #include "clixon_string.h"
92 #include "clixon_err.h"
93 #include "clixon_log.h"
94 #include "clixon_options.h"
95 #include "clixon_yang.h"
96 #include "clixon_xml.h"
97 #include "clixon_xml_nsctx.h"
98 #include "clixon_xml_vec.h"
99 #include "clixon_xml_sort.h"
100 #include "clixon_netconf_lib.h"
101 #include "clixon_xml_map.h"
102 #include "clixon_yang_module.h"
103 #include "clixon_path.h"
104 #include "clixon_api_path_parse.h"
105 #include "clixon_instance_id_parse.h"
106 
107 /*! Given api-path, parse it, and return a clixon-path struct
108  *
109  * @param[in]  api_path  String with api-path syntax according to RESTCONF RFC8040
110  * @param[out] cplist    Structured internal clixon-path
111  * @retval     0         OK
112  * @retval    -1         Error
113  * @code
114  *   clixon_path  *cplist = NULL;
115  *   if (api_path_parse(api_path, &cplist) < 0)
116  *     err;
117  *   if (api_path_resolve(cplist, yt) < 0)
118  *     err;
119  *   ...
120  *   if (cplist)
121  *	clixon_path_free(cplist);
122  * @endcode
123  * @see clixon_path_free
124  */
125 static int
api_path_parse(char * api_path,clixon_path ** cplist)126 api_path_parse(char         *api_path,
127 	       clixon_path **cplist)
128 {
129     int                  retval = -1;
130     clixon_api_path_yacc ay = {0,};
131 
132     clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
133     ay.ay_parse_string = api_path;
134     ay.ay_name = "api-path parser";
135     ay.ay_linenum = 1;
136     if (api_path_scan_init(&ay) < 0)
137 	goto done;
138     if (api_path_parse_init(&ay) < 0)
139 	goto done;
140     if (clixon_api_path_parseparse(&ay) != 0) { /* yacc returns 1 on error */
141 	clicon_log(LOG_NOTICE, "API-PATH error: on line %d", ay.ay_linenum);
142 	if (clicon_errno == 0)
143 	    clicon_err(OE_XML, 0, "API-PATH parser error with no error code (should not happen)");
144 	goto done;
145     }
146     api_path_parse_exit(&ay);
147     api_path_scan_exit(&ay);
148     *cplist = ay.ay_top;
149     retval = 0;
150  done:
151     return retval;
152 }
153 
154 /*! Given instance-id path, parse it, and return an clixon-path struct
155  *
156  * @param[in]  api_path  String with syntax according to YANG RFC7980
157  * @param[out] cplist    Structured internal clixon-path
158  * @retval     0         OK
159  * @retval    -1         Error
160  * @code
161  *   clixon_path  *cplist = NULL;
162  *   if (instance_id_parse(api_path, &cplist) < 0)
163  *     err;
164  *   ...
165  *   if (cplist)
166  *	clixon_path_free(cplist);
167  * @endcode
168  * @see clixon_path_free
169  */
170 static int
instance_id_parse(char * path,clixon_path ** cplist)171 instance_id_parse(char         *path,
172 		  clixon_path **cplist)
173 {
174     int                     retval = -1;
175     clixon_instance_id_yacc iy = {0,};
176 
177     clicon_debug(1, "%s path:%s", __FUNCTION__, path);
178     iy.iy_parse_string = path;
179     iy.iy_name = "instance-id parser";
180     iy.iy_linenum = 1;
181     if (instance_id_scan_init(&iy) < 0)
182 	goto done;
183     if (instance_id_parse_init(&iy) < 0)
184 	goto done;
185     if (clixon_instance_id_parseparse(&iy) != 0) { /* yacc returns 1 on error */
186 	clicon_log(LOG_NOTICE, "Instance-id error: on line %d", iy.iy_linenum);
187 	if (clicon_errno == 0)
188 	    clicon_err(OE_XML, 0, "Instance-id parser error with no error code (should not happen)");
189 	goto done;
190     }
191     instance_id_parse_exit(&iy);
192     instance_id_scan_exit(&iy);
193     *cplist = iy.iy_top;
194     retval = 0;
195  done:
196     return retval;
197 }
198 
199 static int
clixon_path_free(clixon_path * cplist)200 clixon_path_free(clixon_path *cplist)
201 {
202     clixon_path *cp;
203 
204     while ((cp = cplist) != NULL){
205 	DELQ(cp, cplist, clixon_path *);
206 	if (cp->cp_prefix)
207 	    free(cp->cp_prefix);
208 	if (cp->cp_id)
209 	    free(cp->cp_id);
210 	if (cp->cp_cvk)
211 	    cvec_free(cp->cp_cvk);
212 	free(cp);
213     }
214     return 0;
215 }
216 
217 /*! Print path on instance-id/xpath form
218  */
219 static int
clixon_path_print(FILE * f,clixon_path * cplist)220 clixon_path_print(FILE        *f,
221 		  clixon_path *cplist)
222 {
223     clixon_path *cp;
224     cg_var      *cv;
225 
226     if ((cp = cplist) != NULL){
227 	do {
228 	    fprintf(f, "/");
229 	    if (cp->cp_prefix)
230 		fprintf(f, "%s:", cp->cp_prefix);
231 	    fprintf(f, "%s", cp->cp_id);
232 	    if (cp->cp_cvk){
233 		fprintf(f, "=");
234 		cv = NULL;
235 		while ((cv = cvec_each(cp->cp_cvk, cv)) != NULL){
236 		    fprintf(f, "[");
237 		    /* If cvk has one integer argument, interpret as position, eg x/y[42] */
238 		    if (cvec_len(cp->cp_cvk) == 1 && (cv_type_get(cv) == CGV_UINT32))
239 			fprintf(f, "%u", cv_uint32_get(cv));
240 		    else
241 			fprintf(f, "%s=\"%s\"", cv_name_get(cv), cv_string_get(cv));
242 		    fprintf(f, "]");
243 		}
244 	    }
245 	    cp = NEXTQ(clixon_path *, cp);
246 	} while (cp && cp != cplist);
247     }
248     fprintf(f, "\n");
249     return 0;
250 }
251 
252 /*! Given an XML node, return root node
253  * A root node is an ancestor xr of x with one or both of the following properties
254  * - its XML parent is NULL parent,
255  * - its associated yang specification's parent is a yang module.
256  * @param[in]   x    XML node
257  * @param[out]  xr   XML root
258  */
259 int
xml_yang_root(cxobj * x,cxobj ** xr)260 xml_yang_root(cxobj  *x,
261 	      cxobj **xr)
262 {
263     int        retval = -1;
264     cxobj     *xp;
265     yang_stmt *y;
266     yang_stmt *yp;
267 
268     while ((xp = xml_parent(x)) != NULL){
269 	if ((y = xml_spec(x)) != NULL &&
270 	    (yp = yang_parent_get(y)) != NULL)
271 	    /* Actually, maybe only the Y_MODULE clause is relevant */
272 	    if (yp==NULL ||
273 		yang_keyword_get(yp) == Y_MODULE ||
274 		yang_keyword_get(yp) == Y_SUBMODULE)
275 		break; /* x is the root */
276 	x = xp;
277     }
278     *xr = x;
279     retval = 0;
280     return retval;
281 }
282 
283 /*! Construct an xml key format from yang statement using wildcards for keys
284  * Recursively construct it to the top.
285  * Example:
286  *   yang:  container a -> list b -> key c -> leaf d
287  *   xpath: /modname:a/b/%s/d
288  * @param[in]  ys      Yang statement
289  * @param[in]  inclkey If set include key leaf (eg last leaf d in ex)
290  * @param[out] cb      api_path_fmt,
291  * @retval     0    OK
292  * @retval    -1    Error
293  * @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression"
294  */
295 static int
yang2api_path_fmt_1(yang_stmt * ys,int inclkey,cbuf * cb)296 yang2api_path_fmt_1(yang_stmt *ys,
297 		    int        inclkey,
298 		    cbuf      *cb)
299 {
300     yang_stmt *yp; /* parent */
301     yang_stmt *ymod = NULL;
302     yang_stmt *ypmod = NULL;
303     int        i;
304     cvec      *cvk = NULL; /* vector of index keys */
305     int        retval = -1;
306 
307     if ((yp = yang_parent_get(ys)) == NULL){
308 	clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
309 	goto done;
310     }
311     if (yp != NULL && /* XXX rm */
312 	yang_keyword_get(yp) != Y_MODULE &&
313 	yang_keyword_get(yp) != Y_SUBMODULE){
314 
315 	if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
316 	    goto done;
317 	if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
318 #if 0
319 	    /* In some cases, such as cli_show_auto, a trailing '/' should
320 	     * NOT be present if ys is a key in a list.
321 	     * But in other cases (I think most), the / should be there,
322 	     * so a patch is added in cli_show_auto instead.
323 	     */
324 		if (yang_keyword_get(ys) == Y_LEAF && yp &&
325 		    yang_keyword_get(yp) == Y_LIST &&
326 		yang_key_match(yp, ys->ys_argument) == 1)
327 		;
328 	    else
329 #endif
330 		cprintf(cb, "/");
331 	}
332 	/* If parent namespace/module is different from child -> add child prefix */
333 	if (ys_real_module(yp, &ypmod) < 0)
334 	    goto done;
335 	if (ys_real_module(ys, &ymod) < 0)
336 	    goto done;
337 	if (ypmod != ymod)
338 	    cprintf(cb, "%s:", yang_argument_get(ymod));
339     }
340     else /* top symbol - mark with name prefix */
341 	cprintf(cb, "/%s:", yang_argument_get(yp));
342 
343     if (inclkey){
344 	if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
345 	    cprintf(cb, "%s", yang_argument_get(ys));
346     }
347     else{
348 	if (yang_keyword_get(ys) == Y_LEAF && yp &&
349 	    yang_keyword_get(yp) == Y_LIST){
350 	    if (yang_key_match(yp, yang_argument_get(ys)) == 0)
351 		cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
352 	}
353 	else
354 	    if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
355 		cprintf(cb, "%s", yang_argument_get(ys));
356     }
357 
358     switch (yang_keyword_get(ys)){
359     case Y_LIST:
360 	cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
361 	if (cvec_len(cvk))
362 	    cprintf(cb, "=");
363 	/* Iterate over individual keys  */
364 	for (i=0; i<cvec_len(cvk); i++){
365 	    if (i)
366 		cprintf(cb, ",");
367 	    cprintf(cb, "%%s");
368 	}
369 	break;
370     case Y_LEAF_LIST:
371 	cprintf(cb, "=%%s");
372 	break;
373     default:
374 	break;
375     } /* switch */
376     retval = 0;
377  done:
378     return retval;
379 }
380 
381 /*! Construct an api_path_format from yang statement using wildcards for keys
382  * Recursively construct it to the top.
383  * Example:
384  *   yang:  container a -> list b -> key c -> leaf d
385  *   api_path: /a/b=%s/d
386  * @param[in]  ys           Yang statement
387  * @param[in]  inclkey      If set include key leaf (eg last leaf d in ex)
388  * @param[out] api_path_fmt XML api path. Needs to be freed after use.
389  * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
390  */
391 int
yang2api_path_fmt(yang_stmt * ys,int inclkey,char ** api_path_fmt)392 yang2api_path_fmt(yang_stmt *ys,
393 		  int        inclkey,
394 		  char     **api_path_fmt)
395 {
396     int   retval = -1;
397     cbuf *cb = NULL;
398 
399     if ((cb = cbuf_new()) == NULL){
400 	clicon_err(OE_UNIX, errno, "cbuf_new");
401 	goto done;
402     }
403     if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
404 	goto done;
405     if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
406 	clicon_err(OE_UNIX, errno, "strdup");
407 	goto done;
408     }
409     retval = 0;
410  done:
411     if (cb)
412 	cbuf_free(cb);
413     return retval;
414 }
415 
416 /*! Transform an xml key format and a vector of values to an XML key
417  * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
418  * Example:
419  *   xmlkeyfmt:  /interfaces/interface=%s/ipv4/address=%s
420  *   cvv:     0 : set interfaces interface e ipv4 address 1.2.3.4
421  *            1 : name = "e"
422  *            2 : ip = "1.2.3.4"
423  *   api_path:  /interfaces/interface=e/ipv4/address=1.2.3.4
424  * @param[in]  api_path_fmt  XML key format, eg /aaa/%s/name
425  * @param[in]  cvv           cligen variable vector, one for every wildchar in api_path_fmt
426  * @param[out] api_path      api_path, eg /aaa/17. Free after use
427  * @note first and last elements of cvv are not used,..
428  * @see api_path_fmt2xpath
429  * @example
430  *  api_path_fmt: /interfaces/interface=%s/name
431  *  cvv:          -
432  *  api_path:     /interfaces/interface/name
433  * @example
434  *  api_path_fmt: /interfaces/interface=%s/name
435  *  cvv:          e0
436  *  api_path:     /interfaces/interface=e0/name
437  * @example
438  *  api_path_fmt: /subif-entry=%s,%s/subid
439  *  cvv:          foo
440  *  api_path:     /subif-entry=foo/subid
441  *
442  * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
443  */
444 int
api_path_fmt2api_path(char * api_path_fmt,cvec * cvv,char ** api_path)445 api_path_fmt2api_path(char  *api_path_fmt,
446 		      cvec  *cvv,
447 		      char **api_path)
448 {
449     int   retval = -1;
450     char  c;
451     int   esc=0;
452     cbuf *cb = NULL;
453     int   i;
454     int   j;
455     char *str;
456     char *strenc=NULL;
457     cg_var *cv;
458 
459     if ((cb = cbuf_new()) == NULL){
460 	clicon_err(OE_UNIX, errno, "cbuf_new");
461 	goto done;
462     }
463     j = 1; /* j==0 is cli string */
464     for (i=0; i<strlen(api_path_fmt); i++){
465 	c = api_path_fmt[i];
466 	if (esc){
467 	    esc = 0;
468 	    if (c!='s')
469 		continue;
470 	    if (j == cvec_len(cvv)) /* last element */
471 		;
472 	    else{
473 		cv = cvec_i(cvv, j++);
474 		if ((str = cv2str_dup(cv)) == NULL){
475 		    clicon_err(OE_UNIX, errno, "cv2str_dup");
476 		    goto done;
477 		}
478 		if (uri_percent_encode(&strenc, "%s", str) < 0)
479 		    goto done;
480 		cprintf(cb, "%s", strenc);
481 		free(strenc); strenc = NULL;
482 		free(str); str = NULL;
483 	    }
484 	}
485 	else
486 	    if (c == '%')
487 		esc++;
488 	    else{
489 		if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%' && j == cvec_len(cvv))
490 		    ; /* skip */
491 		else
492 		    cprintf(cb, "%c", c);
493 	    }
494     }
495     if ((*api_path = strdup(cbuf_get(cb))) == NULL){
496 	clicon_err(OE_UNIX, errno, "strdup");
497 	goto done;
498     }
499     retval = 0;
500  done:
501     if (cb)
502 	cbuf_free(cb);
503     return retval;
504 }
505 
506 /*! Transform an xml key format and a vector of values to an XML path
507  * Used to input xmldb_get()
508  * @param[in]  api_path_fmt  XML key format
509  * @param[in]  cvv    cligen variable vector, one for every wildchar in api_path_fmt
510  * @param[out] xpath     XPATH
511  * Add .* in last %s position.
512  * @example
513  *   api_path_fmt:  /interface/%s/address/%s
514  *   cvv:           name=eth0
515  *   xpath:         /interface/[name='eth0']/address
516  * @example
517  *   api_path_fmt:  /ip/me/%s (if key)
518  *   cvv:           -
519  *   xpath:        /ipv4/me/a
520  * @example
521  *   api_path_fmt: /subif-entry=%s,%s/subid
522  *   cvv:          foo
523  *   xpath:        /subif-entry[if-name=foo]/subid"
524  * @example
525  *   api_path_fmt: /a:b/c
526  *   xpath       : /b/c prefix:a
527  * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
528  */
529 int
api_path_fmt2xpath(char * api_path_fmt,cvec * cvv,char ** xpath)530 api_path_fmt2xpath(char  *api_path_fmt,
531 		   cvec  *cvv,
532 		   char **xpath)
533 {
534     int     retval = -1;
535     char    c;
536     int     esc=0;
537     cbuf   *cb = NULL;
538     int     i;
539     int     j;
540     char   *str;
541     cg_var *cv;
542 
543     if ((cb = cbuf_new()) == NULL){
544 	clicon_err(OE_UNIX, errno, "cbuf_new");
545 	goto done;
546     }
547     j = 1; /* j==0 is cli string */
548     for (i=0; i<strlen(api_path_fmt); i++){
549 	c = api_path_fmt[i];
550 	if (esc){
551 	    esc = 0;
552 	    if (c!='s')
553 		continue;
554 	    if (j == cvec_len(cvv)) /* last element */
555 		;
556 	    else{
557 		cv = cvec_i(cvv, j++);
558 		if ((str = cv2str_dup(cv)) == NULL){
559 		    clicon_err(OE_UNIX, errno, "cv2str_dup");
560 		    goto done;
561 		}
562 		cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
563 		free(str);
564 	    }
565 	}
566 	else /* regular char */
567 	    if (c == '%')
568 		esc++;
569 	    else{
570 		if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
571 		    ; /* skip */
572 		else
573 		    cprintf(cb, "%c", c);
574 	    }
575     }
576     if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
577 	clicon_err(OE_UNIX, errno, "strdup");
578 	goto done;
579     }
580     retval = 0;
581  done:
582     if (cb)
583 	cbuf_free(cb);
584     return retval;
585 }
586 
587 /*! Translate from restconf api-path(cvv) to xml xpath(cbuf) and namespace context
588  *
589  * @param[in]     api_path URI-encoded path expression" (RFC8040 3.5.3) as cvec
590  * @param[in]     offset   Offset of cvec, where api-path starts
591  * @param[in]     yspec    Yang spec
592  * @param[in,out] xpath    The xpath as cbuf (must be created and may have content)
593  * @param[out]    nsc      Namespace context of xpath (free w xml_nsctx_free)
594  * @param[out]    xerr     Netconf error message
595  * @retval        1        OK
596  * @retval        0        Invalid api_path or associated XML, netconf error xml set
597  * @retval       -1        Fatal error, clicon_err called
598  *
599  * @code
600  *   cbuf *xpath = cbuf_new();
601  *   cvec *cvv = NULL;
602  *   cvec *nsc = NULL;
603  *   if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0)
604  *      err;
605  *   if ((ret = api_path2xpath_cvv(yspec, cvv, 0, cxpath, &nsc, NULL)) < 0)
606  *      err;
607  *   if (ret == 1)
608  *     ... access xpath as cbuf_get(xpath)
609  *   cbuf_free(xpath);
610  *   cvec_free(nsc);
611  * @endcode
612  * It works like this:
613  * Assume origin incoming path is
614  * "www.foo.com/restconf/a/b=c",  pi is 2 and pcvec is:
615  *   ["www.foo.com" "restconf" "a" "b=c"]
616  * which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
617  * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
618  * @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
619  * @note Not proper namespace translation from api-path 2 xpath!!!
620  * @see api_path2xml  For api-path to xml tree
621  * @see api_path2xpath  Using strings as parameters
622  */
623 static int
api_path2xpath_cvv(cvec * api_path,int offset,yang_stmt * yspec,cbuf * xpath,cvec ** nscp,cxobj ** xerr)624 api_path2xpath_cvv(cvec       *api_path,
625 		   int         offset,
626 		   yang_stmt  *yspec,
627 		   cbuf       *xpath,
628 		   cvec      **nscp,
629 		   cxobj     **xerr)
630 {
631     int        retval = -1;
632     int        i;
633     cg_var    *cv;
634     char      *nodeid;
635     char      *prefix = NULL;  /* api-path (module) prefix */
636     char      *xprefix = NULL; /* xml xpath prefix */
637     char      *name = NULL;
638     cvec      *cvk = NULL; /* vector of index keys */
639     yang_stmt *y = NULL;
640     yang_stmt *ymod = NULL;
641     char      *val;
642     cg_var    *cvi;
643     char     **valvec = NULL;
644     int        vi;
645     int        nvalvec;
646     cbuf      *cberr = NULL;
647     char      *namespace = NULL;
648     cvec      *nsc = NULL;
649 
650     cprintf(xpath, "/");
651     /* Initialize namespace context */
652     if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
653 	goto done;
654     if ((cberr = cbuf_new()) == NULL){
655 	clicon_err(OE_UNIX, errno, "cbuf_new");
656 	goto done;
657     }
658     for (i=offset; i<cvec_len(api_path); i++){
659         cv = cvec_i(api_path, i);
660 	nodeid = cv_name_get(cv);
661 	/* api-path: prefix points to module */
662 	if (nodeid_split(nodeid, &prefix, &name) < 0)
663 	    goto done;
664 	clicon_debug(1, "%s [%d] cvname: %s:%s",
665 		     __FUNCTION__, i, prefix?prefix:"", name);
666 	/* top-node must have prefix */
667 	if (i == offset && prefix == NULL){
668 	    cprintf(cberr, "'%s': Expected prefix:name", nodeid);
669 	    if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
670 		goto done;
671 	    goto fail;
672 	}
673 	ymod = NULL;
674 	if (prefix){ /* if prefix -> get module + change namespace */
675 	    if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
676 		cprintf(cberr, "No such yang module: %s", prefix);
677 		if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
678 		    goto done;
679 		goto fail;
680 	    }
681 	    namespace = yang_find_mynamespace(ymod); /* change namespace */
682 	}
683 	if (i == offset && ymod) /* root */
684 	    y = yang_find_datanode(ymod, name);
685 	else
686 	    y = yang_find_datanode(y, name);
687 	if (y == NULL){
688 	    if (xerr && netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
689 		goto done;
690 	    goto fail;
691 	}
692 
693 	/* Get XML/xpath prefix given namespace.
694 	 * note different from api-path prefix
695 	 */
696 	if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
697 	    xprefix = yang_find_myprefix(y);
698 	    clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
699 	    /* not found, add it to nsc
700 	     * XXX: do we always have to add it? It could be default?
701 	     */
702 	    //	    if (xml2prefix(x1, namespace, &pexisting));
703 	    if (xml_nsctx_add(nsc, xprefix, namespace) < 0)
704 		goto done;
705 	}
706 	/* Check if has value, means '=' */
707         if (cv2str(cv, NULL, 0) > 0){
708             if ((val = cv2str_dup(cv)) == NULL)
709                 goto done;
710 	    switch (yang_keyword_get(y)){
711 	    case Y_LIST:
712 		/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
713 		 * Note that vnr can be < length of cvk, due to empty or unset values
714 		 */
715 		if (valvec){ /* loop, valvec may have been used before */
716 		    free(valvec);
717 		    valvec = NULL;
718 		}
719 		if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
720 		    goto done;
721 		cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
722 		cvi = NULL;
723 		/* Iterate over individual yang keys  */
724 		cprintf(xpath, "/");
725 		if (xprefix)
726 		    cprintf(xpath, "%s:", xprefix);
727 		cprintf(xpath, "%s", name);
728 		vi = 0;
729 		while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
730 		    cprintf(xpath, "[");
731 		    if (xprefix)
732 			cprintf(xpath, "%s:", xprefix);
733 		    cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
734 		}
735 		break;
736 	    case Y_LEAF_LIST: /* XXX: LOOP? */
737 		cprintf(xpath, "/");
738 		if (xprefix)
739 		    cprintf(xpath, "%s:", xprefix);
740 		cprintf(xpath, "%s", name);
741 		if (val)
742 		    cprintf(xpath, "[.='%s']", val);
743 		else
744 		    cprintf(xpath, "[.='']");
745 		break;
746 	    default:
747 		if (i != offset)
748 		    cprintf(xpath, "/");
749 		if (xprefix)
750 		    cprintf(xpath, "%s:", xprefix);
751 		cprintf(xpath, "%s", name);
752 		break;
753 	    }
754 	    if (val)
755 		free(val);
756         }
757         else{
758 	    if (i != offset)
759 		cprintf(xpath, "/");
760 	    if (xprefix)
761 		cprintf(xpath, "%s:", xprefix);
762 	    cprintf(xpath, "%s", name);
763 	}
764 	if (prefix){
765 	    free(prefix);
766 	    prefix = NULL;
767 	}
768 	if (name){
769 	    free(name);
770 	    name = NULL;
771 	}
772     } /* for */
773     retval = 1; /* OK */
774     if (nscp){
775 	*nscp = nsc;
776 	nsc = NULL;
777     }
778  done:
779     clicon_debug(2, "%s retval:%d", __FUNCTION__, retval);
780     if (cberr)
781 	cbuf_free(cberr);
782     if (valvec)
783 	free(valvec);
784     if (prefix)
785 	free(prefix);
786     if (nsc)
787 	cvec_free(nsc);
788     if (name)
789 	free(name);
790     return retval;
791  fail:
792     retval = 0; /* Validation failed */
793     goto done;
794 }
795 
796 /*! Translate from restconf api-path to xml xpath and namespace
797  * @param[in]  api_path  URI-encoded path expression" (RFC8040 3.5.3)
798  * @param[in]  yspec     Yang spec
799  * @param[out] xpath     xpath (use free() to deallocate)
800  * @param[out] nsc       Namespace context of xpath (free w xml_nsctx_free)
801  * @param[out] xerr      Netconf error message
802  * @retval     1         OK
803  * @retval     0         Invalid api_path or associated XML, netconf called
804  * @retval    -1         Fatal error, clicon_err called
805  * @code
806  *   char *xpath = NULL;
807  *   cvec *nsc = NULL;
808  *   if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc)) < 0)
809  *      err;
810  *   if (ret == 1)
811  *      ... access xpath as cbuf_get(xpath)
812  *   free(xpath)
813  *   cvec_free(nsc);
814  * @endcode
815  * @see api_path2xml  For api-path to xml translation (maybe could be combined?)
816  */
817 int
api_path2xpath(char * api_path,yang_stmt * yspec,char ** xpathp,cvec ** nsc,cxobj ** xerr)818 api_path2xpath(char       *api_path,
819 	       yang_stmt  *yspec,
820 	       char      **xpathp,
821 	       cvec      **nsc,
822 	       cxobj     **xerr)
823 {
824     int    retval = -1;
825     cvec  *cvv = NULL; /* api-path vector */
826     cbuf  *xpath = NULL; /* xpath as cbuf (sub-function uses that) */
827     int    ret;
828 
829     if (api_path == NULL){
830 	clicon_err(OE_XML, EINVAL, "api_path is NULL");
831 	goto done;
832     }
833     /* Split api-path into cligen variable vector */
834     if (str2cvec(api_path, '/', '=', &cvv) < 0)
835 	goto done;
836     if ((xpath = cbuf_new()) == NULL)
837         goto done;
838     if ((ret = api_path2xpath_cvv(cvv, 0, yspec, xpath, nsc, xerr)) < 0)
839 	goto done;
840     if (ret == 0)
841 	goto fail;
842     /* prepare output xpath parameter */
843     if (xpathp)
844 	if ((*xpathp = strdup(cbuf_get(xpath))) == NULL){
845 	    clicon_err(OE_UNIX, errno, "strdup");
846 	    goto done;
847 	}
848     retval = 1;
849  done:
850     if (cvv)
851 	cvec_free(cvv);
852     if (xpath)
853 	cbuf_free(xpath);
854     return retval;
855  fail:
856     retval = 0; /* Validation failed */
857     goto done;
858 }
859 
860 /*! Create xml tree from api-path as vector
861  * @param[in]   vec       APIpath as char* vector
862  * @param[in]   nvec      Length of vec
863  * @param[in]   x0        Xpath tree so far
864  * @param[in]   y0        Yang spec for x0
865  * @param[in]   nodeclass Set to schema nodes, data nodes, etc
866  * @param[out]  xbotp     Resulting xml tree
867  * @param[out]  ybotp    Yang spec matching xpathp
868  * @param[out]  xerr      Netconf error message (if retval=0)
869  * @retval      1         OK
870  * @retval      0         Invalid api_path or associated XML, netconf error
871  * @retval     -1         Fatal error, clicon_err called
872  *
873  * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
874  * @see api_path2xpath For api-path to xml xpath translation
875  * @see api_path2xml
876  */
877 static int
api_path2xml_vec(char ** vec,int nvec,cxobj * x0,yang_stmt * y0,yang_class nodeclass,int strict,cxobj ** xbotp,yang_stmt ** ybotp,cxobj ** xerr)878 api_path2xml_vec(char      **vec,
879 		 int         nvec,
880 		 cxobj      *x0,
881 		 yang_stmt  *y0,
882 		 yang_class  nodeclass,
883 		 int         strict,
884 		 cxobj     **xbotp,
885 		 yang_stmt **ybotp,
886 		 cxobj     **xerr)
887 {
888     int        retval = -1;
889     char      *nodeid;
890     char      *name = NULL;
891     char      *prefix = NULL;
892     char      *restval = NULL;
893     char      *restval_enc;
894     cxobj     *xn = NULL; /* new */
895     cxobj     *xb;        /* body */
896     cvec      *cvk = NULL; /* vector of index keys */
897     cg_var    *cvi;
898     char      *keyname;
899     char     **valvec = NULL;
900     int        nvalvec = 0;
901     int        vi;
902     cxobj     *x = NULL;
903     yang_stmt *y = NULL;
904     yang_stmt *ymod;
905     yang_stmt *ykey;
906     char      *namespace = NULL;
907     cbuf      *cberr = NULL;
908 
909     if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
910 	if (xbotp)
911 	    *xbotp = x0;
912 	if (ybotp)
913 	    *ybotp = y0;
914 	goto ok;
915     } /* E.g "x=1,2" -> nodeid:x restval=1,2 */
916     if ((cberr = cbuf_new()) == NULL){
917 	clicon_err(OE_UNIX, errno, "cbuf_new");
918 	goto done;
919     }
920 
921     /* restval is RFC 3896 encoded */
922     if ((restval_enc = index(nodeid, '=')) != NULL){
923 	*restval_enc = '\0';
924 	restval_enc++;
925 	if (uri_percent_decode(restval_enc, &restval) < 0)
926 	    goto done;
927     }
928     /* Split into prefix and localname */
929     if (nodeid_split(nodeid, &prefix, &name) < 0)
930 	goto done;
931     if (yang_keyword_get(y0) == Y_SPEC){ /* top-node */
932 	if (prefix == NULL){
933 	    cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid);
934 	    if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
935 		goto done;
936 	    goto fail;
937 	}
938 	if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
939 	    cprintf(cberr, "No such yang module prefix");
940 	    if (netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0)
941 		goto done;
942 	    goto fail;
943 	}
944 	namespace = yang_find_mynamespace(ymod);
945 	y0 = ymod;
946     }
947     y = (nodeclass==YC_SCHEMANODE)?
948 	yang_find_schemanode(y0, name):
949 	yang_find_datanode(y0, name);
950     if (y == NULL){
951 	if (netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
952 	    goto done;
953 	goto fail;
954     }
955     if (prefix && namespace == NULL){
956 	if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
957 	    cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix);
958 	    if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
959 		goto done;
960 	    goto fail;
961 	}
962 	namespace = yang_find_mynamespace(ymod);
963     }
964     switch (yang_keyword_get(y)){
965     case Y_LEAF_LIST:
966 	if (0 && restval==NULL){
967 	    clicon_err(OE_XML, 0, "malformed key, expected '=restval'");
968 	    goto done;
969 	}
970 	if ((x = xml_new(yang_argument_get(y), x0, CX_ELMNT)) == NULL)
971 	    goto done;
972 	xml_spec_set(x, y);
973 
974 	if ((xb = xml_new("body", x, CX_BODY)) == NULL)
975 	    goto done;
976 	if (restval && xml_value_set(xb, restval) < 0)
977 	    goto done;
978 	break;
979     case Y_LIST:
980 	cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
981 	if (valvec){ /* loop, valvec may have been used before */
982 	    free(valvec);
983 	    valvec = NULL;
984 	}
985 	if (restval==NULL){
986 	    if (strict){
987 		cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
988 		if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
989 		    goto done;
990 	        goto fail;
991 	    }
992 	}
993 	else{
994 	    /* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3)
995 	     * Note that vnr can be < length of cvk, due to empty or unset values
996 	     */
997 	    if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
998 		goto done;
999 	    if ((nvalvec != cvec_len(cvk)) && strict){
1000 		cprintf(cberr, "List key %s length mismatch", name);
1001 		if (netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
1002 		    goto done;
1003 		goto fail;
1004 	    }
1005 	}
1006 	cvi = NULL;
1007 	/* create list object */
1008 	if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
1009 	    goto done;
1010 	xml_spec_set(x, y);
1011 
1012 	vi = 0;
1013 	/* Create keys */
1014 	while ((cvi = cvec_each(cvk, cvi)) != NULL){
1015 	    keyname = cv_string_get(cvi);
1016 	    if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
1017 		cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"",
1018 			yang_argument_get(y), keyname);
1019 		if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
1020 		    goto done;
1021 		goto fail;
1022 	    }
1023 	    if ((xn = xml_new(keyname, x, CX_ELMNT)) == NULL)
1024 		goto done;
1025 	    xml_spec_set(xn, ykey);
1026 
1027 	    if ((xb = xml_new("body", xn, CX_BODY)) == NULL)
1028 		goto done;
1029 	    if (vi++ < nvalvec){
1030 		if (xml_value_set(xb, valvec[vi-1]) < 0)
1031 		    goto done;
1032 	    }
1033 	}
1034 	break;
1035     default: /* eg Y_CONTAINER, Y_LEAF */
1036 	if ((x = xml_find_type(x0, NULL, name, CX_ELMNT)) == NULL){ /* eg key of list */
1037 	    if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
1038 		goto done;
1039 	    xml_spec_set(x, y);
1040 	}
1041 	break;
1042     }
1043     if (x && namespace){
1044 	if (xmlns_set(x, NULL, namespace) < 0)
1045 	    goto done;
1046     }
1047     if ((retval = api_path2xml_vec(vec+1, nvec-1,
1048 				   x, y,
1049 				   nodeclass, strict,
1050 				   xbotp, ybotp, xerr)) < 1)
1051 	goto done;
1052  ok:
1053     retval = 1; /* OK */
1054  done:
1055     clicon_debug(2, "%s retval:%d", __FUNCTION__, retval);
1056     if (cberr)
1057 	cbuf_free(cberr);
1058     if (prefix)
1059 	free(prefix);
1060     if (name)
1061 	free(name);
1062     if (restval)
1063 	free(restval);
1064     if (valvec)
1065 	free(valvec);
1066     return retval;
1067  fail:
1068     retval = 0; /* invalid api-path or XML */
1069     goto done;
1070 }
1071 
1072 /*! Create xml tree from api-path
1073  * @param[in]     api_path   (Absolute) API-path as defined in RFC 8040
1074  * @param[in]     yspec      Yang spec
1075  * @param[in,out] xtop       Incoming XML tree
1076  * @param[in]     nodeclass  Set to schema nodes, data nodes, etc
1077  * @param[out]    xbotp      Resulting xml tree (end of xpath)
1078  * @param[out]    ybotp      Yang spec matching xbotp
1079  * @param[out]    xerr       Netconf error message (if retval=0)
1080  * @retval        1          OK
1081  * @retval        0          Invalid api_path or associated XML, netconf error
1082  * @retval       -1          Fatal error, clicon_err called
1083  * @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
1084  * @example
1085  *   api_path: /subif-entry=foo/subid
1086  *   xtop[in]  <config/>
1087  *   xtop[out]:<config/> <subif-entry>
1088  *                            <if-name>foo<if-name><subid/>>
1089  *                       </subif-entry></config>
1090  *   xbotp:    <subid/>
1091  *   ybotp:    Y_LEAF subid
1092  * @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
1093  * @see api_path2xpath   For api-path to xpath translation (maybe could be combined?)
1094  */
1095 int
api_path2xml(char * api_path,yang_stmt * yspec,cxobj * xtop,yang_class nodeclass,int strict,cxobj ** xbotp,yang_stmt ** ybotp,cxobj ** xerr)1096 api_path2xml(char       *api_path,
1097 	     yang_stmt  *yspec,
1098 	     cxobj      *xtop,
1099 	     yang_class  nodeclass,
1100 	     int         strict,
1101 	     cxobj     **xbotp,
1102 	     yang_stmt **ybotp,
1103 	     cxobj     **xerr)
1104 {
1105     int    retval = -1;
1106     char **vec = NULL;
1107     int    nvec;
1108     cxobj *xroot;
1109     cbuf  *cberr = NULL;
1110 
1111     clicon_debug(2, "%s api_path:%s", __FUNCTION__, api_path);
1112     if (xtop == NULL){
1113 	clicon_err(OE_XML, EINVAL, "xtop is NULL");
1114 	goto done;
1115     }
1116     if ((cberr = cbuf_new()) == NULL){
1117 	clicon_err(OE_UNIX, errno, "cbuf_new");
1118 	goto done;
1119     }
1120     if (*api_path != '/'){
1121 	cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path);
1122 	if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
1123 	    goto done;
1124 	goto fail;
1125     }
1126     if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
1127 	goto done;
1128     /* Remove trailing '/'. Like in /a/ -> /a */
1129     if (nvec > 1 && !strlen(vec[nvec-1]))
1130 	nvec--;
1131     if (nvec < 1){
1132 	cprintf(cberr, "Malformed api-path: %s: too short)", api_path);
1133 	if (netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
1134 	    goto done;
1135 	goto fail;
1136     }
1137     nvec--; /* NULL-terminated */
1138     if ((retval = api_path2xml_vec(vec+1, nvec,
1139 				   xtop, yspec, nodeclass, strict,
1140 				   xbotp, ybotp, xerr)) < 1)
1141 	goto done;
1142     xml_yang_root(*xbotp, &xroot);
1143     if (xmlns_assign(xroot) < 0)
1144 	goto done;
1145     // ok:
1146     retval = 1;
1147  done:
1148     if (cberr)
1149 	cbuf_free(cberr);
1150     if (vec)
1151 	free(vec);
1152     return retval;
1153  fail:
1154     retval = 0;
1155     goto done;
1156 }
1157 
1158 /*! Construct an api_path from an XML node (single level not recursive)
1159  * @param[in]  x     XML node (need to be yang populated)
1160  * @param[out] cb    api_path, must be initialized
1161  * @retval     0     OK
1162  * @retval    -1     Error
1163  * @see yang2api_path_fmt
1164  * @see xml2xpath
1165  */
1166 int
xml2api_path_1(cxobj * x,cbuf * cb)1167 xml2api_path_1(cxobj *x,
1168 
1169 	       cbuf  *cb)
1170 {
1171     int           retval = -1;
1172     yang_stmt    *y = NULL;
1173     cvec         *cvk = NULL; /* vector of index keys */
1174     cg_var       *cvi;
1175     enum rfc_6020 keyword;
1176     int           i;
1177     char         *keyname;
1178     cxobj        *xkey;
1179     cxobj        *xb;
1180     char         *b;
1181     char         *enc;
1182     yang_stmt    *ymod;
1183     cxobj        *xp;
1184 
1185     if ((y = xml_spec(x)) == NULL){
1186 	cprintf(cb, "/%s", xml_name(x));
1187 	goto ok;
1188     }
1189     ymod = ys_module(y);
1190     xp = xml_parent(x);
1191     if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */
1192 	cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x));
1193     else
1194 	cprintf(cb, "/%s", xml_name(x));
1195     keyword = yang_keyword_get(y);
1196     switch (keyword){
1197     case Y_LEAF_LIST:
1198 	b = xml_body(x);
1199 	enc = NULL;
1200 	if (uri_percent_encode(&enc, "%s", b) < 0)
1201 	    goto done;
1202 	cprintf(cb, "=%s", enc?enc:"");
1203 	if (enc)
1204 	    free(enc);
1205 	break;
1206     case Y_LIST:
1207 	cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
1208 	if (cvec_len(cvk))
1209 	    cprintf(cb, "=");
1210 	/* Iterate over individual keys  */
1211 	cvi = NULL;
1212 	i = 0;
1213 	while ((cvi = cvec_each(cvk, cvi)) != NULL) {
1214 	    keyname = cv_string_get(cvi);
1215 	    if ((xkey = xml_find(x, keyname)) == NULL)
1216 		goto done; /* No key in xml */
1217 	    if ((xb = xml_find(x, keyname)) == NULL)
1218 		goto done;
1219 	    if (i++)
1220 		cprintf(cb, ",");
1221 	    b = xml_body(xb);
1222 	    enc = NULL;
1223 	    if (uri_percent_encode(&enc, "%s", b) < 0)
1224 		goto done;
1225 	    cprintf(cb, "%s", enc?enc:"");
1226 	    if (enc)
1227 		free(enc);
1228 	}
1229 	break;
1230     default:
1231 	break;
1232     }
1233 #if 0
1234     { /* Just for testing */
1235 	cxobj *xc;
1236 	if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
1237 	    if (xml2api_path_1(xc, cb) < 0)
1238 		goto done;
1239     }
1240 #endif
1241  ok:
1242     retval = 0;
1243  done:
1244     return retval;
1245 }
1246 
1247 /*! Resolve api-path module:names to yang statements
1248  * @param[in]  cplist   Lisp of clixon-path
1249  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1250  * @retval    -1        Error
1251  * @retval     0        Fail
1252  * @retval     1        OK
1253  * Reasons for fail (retval = 0) are: XXX
1254  * - Modulename in api-path does not correspond to existing module
1255  * - Modulename not defined for top-level id.
1256  * - Corresponding yang node for id not found
1257  * - Number of keys in key-value list does not match Yang list
1258  * - Key-values only defined for list or leaf-list
1259  * @see instance_id_resolve
1260  */
1261 static int
api_path_resolve(clixon_path * cplist,yang_stmt * yt)1262 api_path_resolve(clixon_path *cplist,
1263 		 yang_stmt   *yt)
1264 {
1265     int          retval = -1;
1266     clixon_path *cp;
1267     yang_stmt   *yc;
1268     int          i;
1269     cg_var      *cva;
1270     cg_var      *cvy;
1271 
1272     if ((cp = cplist) != NULL){
1273 	do {
1274 	    if (yang_keyword_get(yt) == Y_SPEC){
1275 		if (cp->cp_prefix == NULL){
1276 		    clicon_err(OE_YANG, ENOENT, "Modulename not defined for top-level id.");
1277 		    goto fail;
1278 		}
1279 		if ((yt = yang_find_module_by_name(yt, cp->cp_prefix)) == NULL){
1280 		    clicon_err(OE_YANG, ENOENT, "Modulename in api-path does not correspond to existing module");
1281 		    goto fail;
1282 		}
1283 	    }
1284 	    if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
1285 		clicon_err(OE_YANG, ENOENT, "Corresponding yang node for id not found");
1286 		goto fail;
1287 	    }
1288 	    cp->cp_yang = yc;
1289 	    if (cp->cp_cvk){
1290 		/* Iterate over yang list keys and assign as names (or "." for leaf-list) in cvk */
1291 		if (yang_keyword_get(yc) == Y_LEAF_LIST){
1292 		    cva = NULL;
1293 		    while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
1294 			if (cv_name_get(cva) == NULL)
1295 			    cv_name_set(cva, ".");
1296 		    }
1297 		}
1298 		else if (yang_keyword_get(yc) == Y_LIST){
1299 		    if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
1300 			clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list");
1301 			goto fail;
1302 		    }
1303 		    i = 0;
1304 		    cva = NULL;
1305 		    while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
1306 			if (cv_name_get(cva) != NULL){
1307 			    clicon_err(OE_YANG, ENOENT, "Unexpected named key %s (shouldnt happen)",
1308 				       cv_name_get(cva));
1309 			    goto fail;
1310 			}
1311 			cvy = cvec_i(yang_cvec_get(yc), i++);
1312 			cv_name_set(cva, cv_string_get(cvy));
1313 		    }
1314 		}
1315 		else{
1316 		    clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
1317 		    goto fail;
1318 		}
1319 	    }
1320 	    yt = yc;
1321     	    cp = NEXTQ(clixon_path *, cp);
1322 	} while (cp && cp != cplist);
1323     }
1324     retval = 1;
1325  done:
1326     return retval;
1327  fail:
1328     retval = 0;
1329     goto done;
1330 }
1331 
1332 /*! Resolve instance-id prefix:names to yang statements
1333  * @param[in]  cplist   Lisp of clixon-path
1334  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1335  * @param[in]  xt       XML statement  for prefix context
1336  * @retval    -1        Error
1337  * @retval     0        Fail
1338  * @retval     1        OK
1339  * @note: The spec says: prefixes depend on the XML context in which the value occurs.
1340  *  However, canonical prefixes/namespaces are used based on loaded yang modules.
1341  *  This means that prefix=NULL is not allowed.
1342  * Reasons for fail (retval = 0) are:
1343  * - No prefix of identifier (keynames may omit prefix)
1344  * - Prefix does not correspond to existing module.
1345  * - Corresponding yang node for id not found
1346  * - Number of keys in key-value list does not match Yang list
1347  * - Key-values only defined for list or leaf-list
1348  * @note key-value presence in lists is not enforced due to use-cases where instance-id need not have
1349  *       them (eg RFC8341) alternative could be to have a "strict" parameter, but gets complicated
1350  * @see api_path_resolve
1351  */
1352 static int
instance_id_resolve(clixon_path * cplist,yang_stmt * yt)1353 instance_id_resolve(clixon_path *cplist,
1354 		    yang_stmt   *yt)
1355 {
1356     int          retval = -1;
1357     clixon_path *cp;
1358     yang_stmt   *yc;
1359     cg_var      *cva;
1360     yang_stmt   *yspec;
1361     char        *kname;
1362 
1363     yspec = ys_spec(yt);
1364     if ((cp = cplist) != NULL){
1365 	do {
1366 	    if (cp->cp_prefix == NULL){
1367 		clicon_err(OE_YANG, ENOENT, "No prefix of identifier (keynames may omit prefix)");
1368 		goto fail;
1369 	    }
1370 	    if (yang_keyword_get(yt) == Y_SPEC){
1371 		if ((yt = yang_find_module_by_prefix_yspec(yspec, cp->cp_prefix)) == NULL){
1372 		    clicon_err(OE_YANG, ENOENT, "Prefix does not correspond to existing module.");
1373 		    goto fail;
1374 		}
1375 	    }
1376 	    if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
1377 		clicon_err(OE_YANG, ENOENT, "Node %s used in path has no corresponding yang node",
1378 			   cp->cp_id);
1379 		goto fail;
1380 	    }
1381 	    cp->cp_yang = yc;
1382 	    switch (yang_keyword_get(yc)){
1383 	    case Y_LIST:
1384 		if (cp->cp_cvk == NULL){
1385 #if 0 /* see key-value presence in lists note above */
1386 		    clicon_err(OE_YANG, ENOENT, "key-values mandatory for lists");
1387 		    goto fail;
1388 #endif
1389 		    break;
1390 		}
1391 #if 0 /* see key-value presence in lists note above */
1392 		if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
1393 		    clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list ");
1394 		    goto fail;
1395 		}
1396 #endif
1397 		cva = NULL;
1398 		while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
1399 		    if ((kname = cv_name_get(cva)) != NULL){ /* Check var exists */
1400 			if (yang_find(yc, 0, kname) == NULL){
1401 			    clicon_err(OE_YANG, ENOENT, "Index variable %s used in path is not child of Yang node %s",
1402 				       kname, yang_argument_get(yc));
1403 			    goto fail;
1404 			}
1405 		    }
1406 		    else{ /* Assume index */
1407 
1408 		    }
1409 		}
1410 		break;
1411 	    case Y_LEAF_LIST:
1412 		break;
1413 	    default:
1414 		if (cp->cp_cvk != NULL){
1415 		    clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
1416 		    goto fail;
1417 		}
1418 		break;
1419 	    }
1420 	    yt = yc;
1421     	    cp = NEXTQ(clixon_path *, cp);
1422 	} while (cp && cp != cplist);
1423     }
1424     retval = 1;
1425  done:
1426     return retval;
1427  fail:
1428     retval = 0;
1429     goto done;
1430 }
1431 
1432 /*! Search XML tree using (internal) Clixon path struct
1433  *
1434  * @param[in]  xt       Top xml-tree where to search
1435  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1436  * @param[in]  cplist   Lisp of clixon-path
1437  * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
1438  * @retval    -1        Error
1439  * @retval     0        Fail  fail: eg no yang
1440  * @retval     1        OK with found xml nodes in xvec (if any)
1441  */
1442 static int
clixon_path_search(cxobj * xt,yang_stmt * yt,clixon_path * cplist,clixon_xvec ** xvec0)1443 clixon_path_search(cxobj        *xt,
1444 		   yang_stmt    *yt,
1445 		   clixon_path  *cplist,
1446 		   clixon_xvec **xvec0)
1447 {
1448     int          retval = -1;
1449     char        *modns; /* Module namespace of api-path */
1450     clixon_path *cp;
1451     clixon_xvec *xvecp = NULL;
1452     clixon_xvec *xvecc = NULL;
1453     yang_stmt   *yc;
1454     cxobj       *xp;
1455     int          i;
1456     cg_var      *cv;
1457 
1458     if ((xvecp = clixon_xvec_new()) == NULL)
1459 	goto done;
1460     modns = NULL;
1461     if ((cp = cplist) != NULL){
1462 	if (clixon_xvec_append(xvecp, xt) < 0)
1463 	    goto done;
1464 	do {
1465 	    yc = cp->cp_yang;
1466 	    if ((modns = yang_find_mynamespace(yc)) == NULL)
1467 		goto fail;
1468 	    if (xvecp){
1469 		if ((xvecc = clixon_xvec_new()) == NULL)
1470 		    goto done;
1471 		for (i=0; i<clixon_xvec_len(xvecp); i++){
1472 		    xp = clixon_xvec_i(xvecp, i); /* Iterate over parent set */
1473 		    if (cp->cp_cvk && /* cornercase for instance-id [<pos>] special case */
1474 			(yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) &&
1475 			cvec_len(cp->cp_cvk) == 1 && (cv = cvec_i(cp->cp_cvk,0)) &&
1476 			(cv_type_get(cv) == CGV_UINT32)){
1477 			if (clixon_xml_find_pos(xp, yc, cv_uint32_get(cv), xvecc) < 0)
1478 			    goto done;
1479 		    }
1480 		    else if (clixon_xml_find_index(xp, yang_parent_get(yc),
1481 						   modns, yang_argument_get(yc),
1482 						   cp->cp_cvk, xvecc) < 0)
1483 			goto done;
1484 		    /* XXX: xvecc append! either here or in the functions */
1485 		} /* for */
1486 	    }
1487 	    if (xvecp)
1488 		clixon_xvec_free(xvecp);
1489 	    xvecp = xvecc;
1490 	    xvecc = NULL;
1491 	    cp = NEXTQ(clixon_path *, cp);
1492 	} while (cp && cp != cplist);
1493 	*xvec0 = xvecp;
1494 	xvecp = NULL;
1495     } /* if */
1496     retval = 1;
1497  done:
1498     if (xvecp)
1499 	clixon_xvec_free(xvecp);
1500     if (xvecc)
1501 	clixon_xvec_free(xvecc);
1502     return retval;
1503  fail: /* eg no yang for api-path, no match */
1504     *xvec0 = NULL;
1505     retval = 0;
1506     goto done;
1507 }
1508 
1509 /*! Given RESTCONF api-path and an XML tree, return matching xml node vector using stdarg
1510  *
1511  * @param[in]  xt       Top xml-tree where to search
1512  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1513  * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
1514  * @param[in]  format   Format string for api-path syntax
1515  * @retval    -1        Error
1516  * @retval     0        Non-fatal failure, yang bind failures, etc,
1517  * @retval     1        OK with found xml nodes in xvec (if any) (xvec contains matches)
1518  * Reasons for nomatch (retval = 0) are:
1519  * - Modulename in api-path does not correspond to existing module
1520  * - Modulename not defined for top-level id.
1521  * - Number of keys in key-value list does not match Yang list
1522  * @code
1523  *    cxobj **vec = NULL;
1524  *    size_t  len = 0;
1525  *    if (clixon_xml_find_api_path(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0)
1526  *       err;
1527  *    for (i=0; i<len; i++){
1528  *       x = vec[i];
1529  *       ...
1530  *    }
1531  *    free(xvec);
1532  * @endcode
1533  * @see clixon_xml_find_instance_id
1534  */
1535 int
clixon_xml_find_api_path(cxobj * xt,yang_stmt * yt,cxobj *** xvec,int * xlen,const char * format,...)1536 clixon_xml_find_api_path(cxobj        *xt,
1537 			 yang_stmt    *yt,
1538 			 cxobj      ***xvec,
1539 			 int          *xlen,
1540 			 const char   *format,
1541 			 ...)
1542 {
1543     int              retval = -1;
1544     va_list          ap;
1545     size_t           len;
1546     char            *api_path = NULL;
1547     clixon_path     *cplist = NULL;
1548     int              ret;
1549     clixon_xvec     *xv = NULL;
1550 
1551     va_start(ap, format);
1552     len = vsnprintf(NULL, 0, format, ap);
1553     va_end(ap);
1554     /* allocate an api-path string exactly fitting the length */
1555     if ((api_path = malloc(len+1)) == NULL){
1556 	clicon_err(OE_UNIX, errno, "malloc");
1557 	goto done;
1558     }
1559     /* second round: actually compute api-path string content */
1560     va_start(ap, format);
1561     if (vsnprintf(api_path, len+1, format, ap) < 0){
1562 	clicon_err(OE_UNIX, errno, "vsnprintf");
1563 	va_end(ap);
1564 	goto done;
1565     }
1566     va_end(ap);
1567     /* Parse api-path string to structured clixon-path data */
1568     if (api_path_parse(api_path, &cplist) < 0)
1569 	goto done;
1570     if (clicon_debug_get())
1571 	clixon_path_print(stderr, cplist);
1572     /* Resolve module:name to yang-stmt, fail if not successful */
1573     if ((ret = api_path_resolve(cplist, yt)) < 0)
1574 	goto done;
1575     if (ret == 0)
1576 	goto fail;
1577     if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
1578 	goto done;
1579     if (ret == 0)
1580 	goto fail;
1581     /* Convert to api xvec format */
1582     if (clixon_xvec_extract(xv, xvec, xlen) < 0)
1583 	goto done;
1584     retval = 1;
1585  done:
1586     if (xv)
1587 	clixon_xvec_free(xv);
1588     if (cplist)
1589 	clixon_path_free(cplist);
1590     if (api_path)
1591 	free(api_path);
1592     return retval;
1593  fail:
1594     retval = 0;
1595     goto done;
1596 }
1597 
1598 /*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
1599  *
1600  * Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for
1601  * example.
1602  * @param[in]  xt       Top xml-tree where to search
1603  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1604  * @param[out] xvec     Vector of xml-trees. Vector must be free():d after use
1605  * @param[out] xlen     Returns length of vector in return value
1606  * @param[in]  format   Format string for api-path syntax
1607  * @retval    -1        Error
1608  * @retval     0        Non-fatal failure, yang bind failures, etc,
1609  * @retval     1        OK with found xml nodes in xvec (if any)
1610  * Reasons for nomatch (retval = 0) are:
1611  * - Modulename in api-path does not correspond to existing module
1612  * - Modulename not defined for top-level id.
1613  * - Number of keys in key-value list does not match Yang list
1614  * @code
1615  *    cxobj **vec = NULL;
1616  *    int     len = 0;
1617  *    if (clixon_xml_find_instance_id(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0)
1618  *       goto err;
1619  *    for (i=0; i<len; i++){
1620  *       x = vec[i];
1621  *       ...
1622  *    }
1623  *    clixon_xvec_free(xvec);
1624  * @endcode
1625  * @note canonical namespace contexts are used, see xpath2canonical
1626  * @see clixon_xml_find_api_path    for RESTCONF api-paths
1627  * @see RFC7950 Sec 9.13
1628  */
1629 int
clixon_xml_find_instance_id(cxobj * xt,yang_stmt * yt,cxobj *** xvec,int * xlen,const char * format,...)1630 clixon_xml_find_instance_id(cxobj     *xt,
1631 			    yang_stmt *yt,
1632 			    cxobj   ***xvec,
1633 			    int       *xlen,
1634 			    const char *format,
1635 			    ...)
1636 {
1637     int          retval = -1;
1638     va_list      ap;
1639     size_t       len;
1640     char        *path = NULL;
1641     clixon_path *cplist = NULL;
1642     int          ret;
1643     clixon_xvec *xv = NULL;
1644 
1645     va_start(ap, format);
1646     len = vsnprintf(NULL, 0, format, ap);
1647     va_end(ap);
1648     /* allocate a path string exactly fitting the length */
1649     if ((path = malloc(len+1)) == NULL){
1650 	clicon_err(OE_UNIX, errno, "malloc");
1651 	goto done;
1652     }
1653     /* second round: actually compute api-path string content */
1654     va_start(ap, format);
1655     if (vsnprintf(path, len+1, format, ap) < 0){
1656 	clicon_err(OE_UNIX, errno, "vsnprintf");
1657 	va_end(ap);
1658 	goto done;
1659     }
1660     va_end(ap);
1661     if (instance_id_parse(path, &cplist) < 0)
1662 	goto done;
1663     if (clicon_debug_get())
1664 	clixon_path_print(stderr, cplist);
1665     /* Resolve module:name to pointer to yang-stmt, fail if not successful */
1666     if ((ret = instance_id_resolve(cplist, yt)) < 0)
1667 	goto done;
1668     if (ret == 0)
1669 	goto fail;
1670     if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
1671 	goto done;
1672     if (ret == 0)
1673 	goto fail;
1674     /* Convert to api xvec format */
1675     if (xv && clixon_xvec_extract(xv, xvec, xlen) < 0)
1676 	goto done;
1677     retval = 1;
1678  done:
1679     if (xv)
1680 	clixon_xvec_free(xv);
1681     if (cplist)
1682 	clixon_path_free(cplist);
1683     if (path)
1684 	free(path);
1685     return retval;
1686  fail:
1687     retval = 0;
1688     goto done;
1689 }
1690 
1691 /*! Given (instance-id) path and YANG, parse path, resolve YANG and return namespace binding
1692  *
1693  * Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for
1694  * example.
1695  * @param[in]  yt       Yang statement of top symbol (can be yang-spec if top-level)
1696  * @param[out] nsctx    Namespace context (should be created on entry)
1697  * @param[in]  format   Format string for api-path syntax
1698  * @retval    -1        Error
1699  * @retval     0        Non-fatal failure, yang bind failures, etc,
1700  * @retval     1        OK with found xml nodes in xvec (if any)
1701  * Reasons for nomatch (retval = 0) are:
1702  * - Modulename in api-path does not correspond to existing module
1703  * - Modulename not defined for top-level id.
1704  * - Number of keys in key-value list does not match Yang list
1705  * @code
1706  *    cvec *nsctx = NULL;
1707  *
1708  *    nsctx = cvec_new(0);
1709  *    if (clixon_instance_id_bind(yspec, nsctx, "/symbol/%s", "foo") < 0)
1710  *       goto err;
1711  *    ...
1712  *    cvec_free(nsctx);
1713  * @endcode
1714  * @note canonical namespace contexts are used, see xpath2canonical
1715  * @see clixon_xml_find_instance_id   for finding XML nodes using instance-id:s
1716  * @see RFC7950 Sec 9.13
1717  */
1718 int
clixon_instance_id_bind(yang_stmt * yt,cvec * nsctx,const char * format,...)1719 clixon_instance_id_bind(yang_stmt  *yt,
1720 			cvec       *nsctx,
1721 			const char *format,
1722 			...)
1723 {
1724     int          retval = -1;
1725     va_list      ap;
1726     size_t       len;
1727     char        *path = NULL;
1728     clixon_path *cplist = NULL;
1729     clixon_path *cp;
1730     int          ret;
1731     char        *namespace;
1732 
1733     va_start(ap, format);
1734     len = vsnprintf(NULL, 0, format, ap);
1735     va_end(ap);
1736     /* allocate a path string exactly fitting the length */
1737     if ((path = malloc(len+1)) == NULL){
1738 	clicon_err(OE_UNIX, errno, "malloc");
1739 	goto done;
1740     }
1741     /* second round: actually compute api-path string content */
1742     va_start(ap, format);
1743     if (vsnprintf(path, len+1, format, ap) < 0){
1744 	clicon_err(OE_UNIX, errno, "vsnprintf");
1745 	va_end(ap);
1746 	goto done;
1747     }
1748     va_end(ap);
1749     if (instance_id_parse(path, &cplist) < 0)
1750 	goto done;
1751     if (clicon_debug_get())
1752 	clixon_path_print(stderr, cplist);
1753     /* Resolve module:name to pointer to yang-stmt, fail if not successful */
1754     if ((ret = instance_id_resolve(cplist, yt)) < 0)
1755 	goto done;
1756     if (ret == 0)
1757 	goto fail;
1758     /* Loop through prefixes used */
1759     if ((cp = cplist) != NULL){
1760 	do {
1761 	    if (cp->cp_prefix &&
1762 		cp->cp_yang &&
1763 		(namespace = yang_find_mynamespace(cp->cp_yang)) != NULL){
1764 		if (xml_nsctx_get(nsctx, cp->cp_prefix) == NULL)
1765 		    if (xml_nsctx_add(nsctx, cp->cp_prefix, namespace) < 0)
1766 			goto done;
1767 	    }
1768 	    cp = NEXTQ(clixon_path *, cp);
1769 	} while (cp && cp != cplist);
1770     }
1771     retval = 1;
1772  done:
1773     if (cplist)
1774 	clixon_path_free(cplist);
1775     if (path)
1776 	free(path);
1777     return retval;
1778  fail:
1779     retval = 0;
1780     goto done;
1781 }
1782