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