diff --git a/CHANGELOG.md b/CHANGELOG.md index 63a869cc..76e06c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ Expected: May 2020 ### Corrected Bugs -* Fixed: Insertion of subtree leaf nodes were not made in the crrect place, always ended up last regardless of yang spec (if ordered-by system). +* Fixed: Insertion of subtree leaf nodes were not made in the correct place, always ended up last regardless of yang spec (if ordered-by system). ## 4.4.0 5 April 2020 diff --git a/LICENSE.md b/LICENSE.md index ad067e78..ae31e385 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren Copyright (C) 2017-2019 Olof Hagsand -Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC +Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC (Netgate) CLIXON is dual license. diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index dadc2e50..3cb936e1 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -52,7 +52,6 @@ typedef struct { char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */ int csm_nsyntax; /* Num syntax specs registered by plugin */ parse_tree csm_pt; /* CLIgen parse tree */ - } cli_syntaxmode_t; /* Plugin group object. Just a single object, not list. part of cli_handle */ diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index bd761e49..21dc4abe 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -273,11 +273,11 @@ expand_dbvar(void *h, /*! List files in a directory */ int -expand_dir(char *dir, - int *nr, +expand_dir(char *dir, + int *nr, char ***commands, - mode_t flags, - int detail) + mode_t flags, + int detail) { DIR *dirp; struct dirent *dp; diff --git a/lib/clixon/clixon_xml_sort.h b/lib/clixon/clixon_xml_sort.h index 9daff2f9..06e45d0d 100644 --- a/lib/clixon/clixon_xml_sort.h +++ b/lib/clixon/clixon_xml_sort.h @@ -50,7 +50,7 @@ int xml_search_indexvar_binary_pos(cxobj *xp, char *indexvar, clixon_xvec *xvec, #endif int match_base_child(cxobj *x0, cxobj *x1c, yang_stmt *yc, cxobj **x0cp); int clixon_xml_find_index(cxobj *xp, yang_stmt *yp, char *namespace, char *name, - cvec *cvk, clixon_xvec **xvec); -int clixon_xml_find_pos(cxobj *xp, yang_stmt *yc, uint32_t pos, clixon_xvec **xvec); + cvec *cvk, clixon_xvec *xvec); +int clixon_xml_find_pos(cxobj *xp, yang_stmt *yc, uint32_t pos, clixon_xvec *xvec); #endif /* _CLIXON_XML_SORT_H */ diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index 4c70d5d9..0bd543f9 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -310,6 +310,48 @@ nacm_rpc(char *rpc, goto done; } +/* Local struct for keeping preparation/compiled data in NACM data path code */ +struct prepvec{ + qelem_t pv_q; + cxobj *pv_xrule; + clixon_xvec *pv_xpathvec; +}; +typedef struct prepvec prepvec; + +/*! Delete all Upgrade callbacks + */ +int +prepvec_free(prepvec *pv_list) +{ + prepvec *pv; + + while((pv = pv_list) != NULL) { + DELQ(pv, pv_list, prepvec *); + if (pv->pv_xpathvec) + clixon_xvec_free(pv->pv_xpathvec); + free(pv); + } + return 0; +} + +prepvec * +prepvec_add(prepvec **pv_listp, + cxobj *xrule) +{ + prepvec *pv; + + if ((pv = malloc(sizeof(*pv))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + return NULL; + } + memset(pv, 0, sizeof(*pv)); + ADDQ(pv, *pv_listp); + pv->pv_xrule = xrule; + if ((pv->pv_xpathvec = clixon_xvec_new()) == NULL) + return NULL; + return pv; +} + /*! Prepare datastructures before running through XML tree * Save rules in a "cache" * These rules match: @@ -318,21 +360,21 @@ nacm_rpc(char *rpc, * Also make instance-id lookups on top object for each rule. Assume at most one result */ static int -nacm_datanode_prepare(clicon_handle h, - cxobj *xt, - enum nacm_access access, - cxobj **gvec, - size_t glen, - cxobj **rlistvec, - size_t rlistlen, - cvec *nsc, - clixon_xvec *rulevec, - clixon_xvec *xpathvec) +nacm_datanode_prepare(clicon_handle h, + cxobj *xt, + enum nacm_access access, + cxobj **gvec, + size_t glen, + cxobj **rlistvec, + size_t rlistlen, + cvec *nsc, + prepvec **pv_listp) { int retval = -1; cxobj *rlist; int i; int j; + int k; char *gname; cxobj **rvec = NULL; /* rules */ size_t rlen; @@ -346,6 +388,7 @@ nacm_datanode_prepare(clicon_handle h, cxobj **xvec = NULL; int xlen = 0; int ret; + prepvec *pv; yspec = clicon_dbspec_yang(h); for (i=0; i 1) - clicon_log(LOG_WARNING, "%s path:%s Clixon only supports single returns, this had: %d", __FUNCTION__, path, xlen); - if (clixon_xvec_append(xpathvec, xvec?xvec[0]:NULL) < 0) /* XXX: vector of vectors? */ + /* Here a new xrule is found, add it */ + if ((pv = prepvec_add(pv_listp, xrule)) == NULL) goto done; + for (k=0; kpv_xpathvec, xvec[k]) < 0) + goto done; + } if (xvec){ free(xvec); xvec = NULL; @@ -438,8 +484,6 @@ nacm_datanode_prepare(clicon_handle h, path = NULL; } } - if (clixon_xvec_append(rulevec, xrule) < 0) /* save it */ - goto done; } if (rvec){ free(rvec); @@ -472,15 +516,17 @@ nacm_datanode_prepare(clicon_handle h, * @retval 2 OK and rule matches permit */ static int -nacm_data_write_xrule_xml(cxobj *xn, - cxobj *xrule, - cxobj *xp, - yang_stmt *yspec) +nacm_data_write_xrule_xml(cxobj *xn, + cxobj *xrule, + clixon_xvec *xpathvec, + yang_stmt *yspec) { int retval = -1; yang_stmt *ymod; char *module_pattern; /* rule module name */ char *action; + cxobj *xp; + int i; if ((module_pattern = xml_find_body(xrule, "module-name")) == NULL) goto nomatch; @@ -502,11 +548,16 @@ nacm_data_write_xrule_xml(cxobj *xn, goto deny; goto permit; } - /* Check if ancestor is xp */ - if (xn != xp && !xml_isancestor(xn, xp)) - goto nomatch; - if (strcmp(action, "deny")==0) - goto deny; + for (i=0; ipv_xrule, pv->pv_xpathvec, yspec)) < 0) goto done; - goto deny; - break; - } - else if (ret == 2) /* Match and accpt: break rule processing but continue recursion */ - break; + switch(ret){ + case 0: /* No match, continue with next rule */ + break; + case 1: /* Match and deny: break all traversal and send error back to client */ + if (netconf_access_denied(cbret, "application", "access denied") < 0) + goto done; + goto deny; + break; + case 2: /* Match and permit: break rule processing but continue recursion */ + break; + } + if (ret == 2) + break; + pv = NEXTQ(prepvec *, pv); + } while (pv && pv != pv_list); } /* If no rule match, check default rule: if deny then break traversal and send error */ if (ret == 0 && !defpermit){ @@ -575,8 +631,7 @@ nacm_datanode_write_recurse(clicon_handle h, /* If node should be purged, dont recurse and defer removal to caller */ x = NULL; /* Recursively check XML */ while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) { - if ((ret = nacm_datanode_write_recurse(h, x, - rulevec, xpathvec, + if ((ret = nacm_datanode_write_recurse(h, x, pv_list, defpermit, yspec, cbret)) < 0) goto done; if (ret == 0) @@ -616,17 +671,16 @@ nacm_datanode_write(clicon_handle h, cxobj *xnacm, cbuf *cbret) { - int retval = -1; - cxobj **gvec = NULL; /* groups */ - size_t glen; - cxobj **rlistvec = NULL; /* rule-list */ - size_t rlistlen; - cxobj **rvec = NULL; /* rules */ - char *write_default = NULL; - cvec *nsc = NULL; - clixon_xvec *rulevec = NULL; - clixon_xvec *xpathvec = NULL; - int ret; + int retval = -1; + cxobj **gvec = NULL; /* groups */ + size_t glen; + cxobj **rlistvec = NULL; /* rule-list */ + size_t rlistlen; + cxobj **rvec = NULL; /* rules */ + char *write_default = NULL; + cvec *nsc = NULL; + int ret; + prepvec *pv_list = NULL; /* Create namespace context for with nacm namespace as default */ if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL) @@ -638,7 +692,6 @@ nacm_datanode_write(clicon_handle h, clicon_err(OE_XML, EINVAL, "No nacm write-default rule"); goto done; } - /* 3. Check all the "group" entries to see if any of them contain a "user-name" entry that equals the username for the session making the request. (If the "enable-external-groups" leaf is @@ -660,16 +713,10 @@ nacm_datanode_write(clicon_handle h, goto done; /* First run through rules and cache rules as well as lookup objects in xt. */ - if ((rulevec = clixon_xvec_new()) == NULL) - goto done; - if ((xpathvec = clixon_xvec_new()) == NULL) - goto done; - if (nacm_datanode_prepare(h, xt, access, gvec, glen, rlistvec, rlistlen, nsc, - rulevec, xpathvec) < 0) + if (nacm_datanode_prepare(h, xt, access, gvec, glen, rlistvec, rlistlen, nsc, &pv_list) < 0) goto done; /* Then recursivelyy traverse all requested nodes */ - if ((ret = nacm_datanode_write_recurse(h, xreq, - rulevec, xpathvec, + if ((ret = nacm_datanode_write_recurse(h, xreq, pv_list, strcmp(write_default, "deny"), clicon_dbspec_yang(h), cbret)) < 0) @@ -701,10 +748,8 @@ nacm_datanode_write(clicon_handle h, retval = 1; done: clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); - if (xpathvec) - clixon_xvec_free(xpathvec); - if (rulevec) - clixon_xvec_free(rulevec); + if (pv_list) + prepvec_free(pv_list); if (nsc) xml_nsctx_free(nsc); if (gvec) @@ -764,13 +809,15 @@ nacm_data_read_action(cxobj *xrule, static int nacm_data_read_xrule_xml(cxobj *xn, cxobj *xrule, - cxobj *xp, + clixon_xvec *xpathvec, yang_stmt *yspec) { int retval = -1; yang_stmt *ymod; char *module_pattern; /* rule module name */ - + cxobj *xp; + int i; + if ((module_pattern = xml_find_body(xrule, "module-name")) == NULL) goto nomatch; /* 6a) The rule's "module-name" leaf is "*" or equals the name of @@ -790,17 +837,21 @@ nacm_data_read_xrule_xml(cxobj *xn, goto done; goto match; } - /* Check if ancestor is xp */ - if (xn != xp && !xml_isancestor(xn, xp)) - goto nomatch; - if (nacm_data_read_action(xrule, xn) < 0) - goto done; - match: - retval = 1; /* match */ - done: - return retval; + for (i=0; ipv_xrule, + pv->pv_xpathvec, + yspec)) < 0) + goto done; + if (ret == 1) + break; /* stop at first match */ + pv = NEXTQ(prepvec *, pv); + } while (pv && pv != pv_list); } + #if 0 /* 6(A) in algorithm * If N did not match any rule R, and default rule is deny, remove that subtree */ if (strcmp(read_default, "deny") == 0) @@ -849,7 +904,7 @@ nacm_datanode_read_recurse(clicon_handle h, x = NULL; /* Recursively check XML */ xprev = NULL; while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) { - if (nacm_datanode_read_recurse(h, x, rulevec, xpathvec, yspec) < 0) + if (nacm_datanode_read_recurse(h, x, pv_list, yspec) < 0) goto done; /* check for delayed remove */ if (xml_flag(x, XML_FLAG_DEL)){ @@ -920,16 +975,15 @@ nacm_datanode_read(clicon_handle h, char *username, cxobj *xnacm) { - int retval = -1; - cxobj **gvec = NULL; /* groups */ - size_t glen; - cxobj **rlistvec = NULL; /* rule-list */ - size_t rlistlen; - int i; - char *read_default = NULL; - cvec *nsc = NULL; - clixon_xvec *rulevec = NULL; - clixon_xvec *xpathvec = NULL; + int retval = -1; + cxobj **gvec = NULL; /* groups */ + size_t glen; + cxobj **rlistvec = NULL; /* rule-list */ + size_t rlistlen; + int i; + char *read_default = NULL; + cvec *nsc = NULL; + prepvec *pv_list = NULL; /* Create namespace context for with nacm namespace as default */ if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL) @@ -960,15 +1014,10 @@ nacm_datanode_read(clicon_handle h, /* First run through rules and cache rules as well as lookup objects in xt. * DANGER: objects could be stale if they are removed? */ - if ((rulevec = clixon_xvec_new()) == NULL) - goto done; - if ((xpathvec = clixon_xvec_new()) == NULL) - goto done; - if (nacm_datanode_prepare(h, xt, NACM_READ, gvec, glen, rlistvec, rlistlen, nsc, - rulevec, xpathvec) < 0) + if (nacm_datanode_prepare(h, xt, NACM_READ, gvec, glen, rlistvec, rlistlen, nsc, &pv_list) < 0) goto done; /* Then recursivelyy traverse all nodes */ - if (nacm_datanode_read_recurse(h, xt, rulevec, xpathvec, clicon_dbspec_yang(h)) < 0) + if (nacm_datanode_read_recurse(h, xt, pv_list, clicon_dbspec_yang(h)) < 0) goto done; #if 1 /* Step 8(B) above: @@ -999,10 +1048,8 @@ nacm_datanode_read(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (xpathvec) - clixon_xvec_free(xpathvec); - if (rulevec) - clixon_xvec_free(rulevec); + if (pv_list) + prepvec_free(pv_list); if (nsc) xml_nsctx_free(nsc); if (gvec) diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index 3b226983..2140e988 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -37,10 +37,16 @@ * - instance-identifier as defined by YANG * - clixon-path is an internal format which both ^ use as internal representation * + * 1. Instance-identifier * "Instance-identifier" is a subset of XML Xpaths and defined in Yang, used in NACM for example. * and defined in RF7950 Sections 9.13 and 14. * To note: prefixes depend on the XML context in which the value occurs, + * In RFC8341 node-instance-identifiers are defined as: + * All the same rules as an instance-identifier apply, except that predicates for keys are optional. + * If a key predicate is missing, then the node-instance-identifier represents all possible server + * instances for that key. * + * 2. Api-path * "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3 * BNF: * := ("/" ( | ))* @@ -1365,31 +1371,40 @@ instance_id_resolve(clixon_path *cplist, goto fail; } cp->cp_yang = yc; - if (cp->cp_cvk){ - /* Iterate over yang list keys and assign as names in cvk */ - if (yang_keyword_get(yc) == Y_LEAF_LIST) - ; - else if (yang_keyword_get(yc) == Y_LIST){ -#ifdef notused - if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){ - clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list "); - goto fail; - } + switch (yang_keyword_get(yc)){ + case Y_LIST: + if (cp->cp_cvk == NULL){ +#if 0 /* XXX why is this not enforced? */ + clicon_err(OE_YANG, ENOENT, "key-values mandatory for lists"); + goto fail; #endif - i = 0; - cva = NULL; - while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) { - if (cv_name_get(cva) == NULL){ - cvy = cvec_i(yang_cvec_get(yc), i); - cv_name_set(cva, cv_string_get(cvy)); - } - i++; + break; + } +#if 0 /* XXX why is this not enforced? */ + if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){ + clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list "); + goto fail; + } +#endif + i = 0; + cva = NULL; + while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) { + if (cv_name_get(cva) == NULL){ + cvy = cvec_i(yang_cvec_get(yc), i); + cv_name_set(cva, cv_string_get(cvy)); } + i++; } - else{ + break; + case Y_LEAF_LIST: + + break; + default: + if (cp->cp_cvk != NULL){ clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list"); - goto fail; + goto fail; } + break; } yt = yc; cp = NEXTQ(clixon_path *, cp); @@ -1439,22 +1454,26 @@ clixon_path_search(cxobj *xt, yc = cp->cp_yang; if ((modns = yang_find_mynamespace(yc)) == NULL) goto fail; - if (xvecp) - for (i=0; icp_cvk && /* cornercase for instance-id [] special case */ - (yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) && - cvec_len(cp->cp_cvk) == 1 && (cv = cvec_i(cp->cp_cvk,0)) && - (cv_type_get(cv) == CGV_UINT32)){ - if (clixon_xml_find_pos(xp, yc, cv_uint32_get(cv), &xvecc) < 0) - goto done; - } - else if (clixon_xml_find_index(xp, yang_parent_get(yc), - modns, yang_argument_get(yc), - cp->cp_cvk, &xvecc) < 0) + if (xvecp){ + if ((xvecc = clixon_xvec_new()) == NULL) goto done; - } /* for */ + for (i=0; icp_cvk && /* cornercase for instance-id [] special case */ + (yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) && + cvec_len(cp->cp_cvk) == 1 && (cv = cvec_i(cp->cp_cvk,0)) && + (cv_type_get(cv) == CGV_UINT32)){ + if (clixon_xml_find_pos(xp, yc, cv_uint32_get(cv), xvecc) < 0) + goto done; + } + else if (clixon_xml_find_index(xp, yang_parent_get(yc), + modns, yang_argument_get(yc), + cp->cp_cvk, xvecc) < 0) + goto done; + /* XXX: xvecc append! either here or in the functions */ + } /* for */ + } if (xvecp) clixon_xvec_free(xvecp); xvecp = xvecc; diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 011c033b..70ef9f52 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -179,6 +179,8 @@ populate_self_parent(cxobj *xt, #endif retval = 1; done: + if (cb) + cbuf_free(cb); return retval; fail: retval = 0; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 95400f7a..f78af4a0 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -735,7 +735,8 @@ xml_search_binary(cxobj *xp, /*! Search XML child under xp matching x1 using yang-based binary search for list/leaf-list keys * * Match is tried xp with x1 with either name only (container/leaf) or using keys (list/leaf-lists) - * Any non-key leafs or other structure of x1 is not matched. + * Any non-key leafs or other structure of x1 is not matched (unless indexvar is set). + * If x1 is list or leaf-list, the function assumes key values / indexvar exists in x1. * * @param[in] xp Parent xml node. * @param[in] x1 Find this object among xp:s children @@ -748,11 +749,11 @@ xml_search_binary(cxobj *xp, * @see xml_find_index for a generic search function */ static int -xml_search_yang(cxobj *xp, - cxobj *x1, - yang_stmt *yc, - int skip1, - char *indexvar, +xml_search_yang(cxobj *xp, + cxobj *x1, + yang_stmt *yc, + int skip1, + char *indexvar, clixon_xvec *xvec) { int retval = -1; @@ -779,7 +780,7 @@ xml_search_yang(cxobj *xp, else #endif if (yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) - sorted = (yang_find(yc, Y_ORDERED_BY, "user") == NULL); + sorted = (yang_find(yc, Y_ORDERED_BY, "user") == NULL); yangi = yang_order(yc); if (xml_search_binary(xp, x1, sorted, yangi, low, upper, skip1, indexvar, xvec) < 0) @@ -1258,7 +1259,7 @@ xml_find_noyang_name(cxobj *xp, /*! Try to find an XML child from parent with yang available using list keys and leaf-lists * * Must be populated with Yang specs, parent must be list or leaf-list, and (for list) search - * index must be keys in the order they are declared. + * index MUST be keys in the order they are declared. * First identify that this search qualifies for yang-based list/leaf-list optimized search, * - if no, revert (return 0) so that the overlying algorithm can try next or fallback to * linear seacrh @@ -1306,6 +1307,10 @@ xml_find_index_yang(cxobj *xp, cprintf(cb, "<%s>", name); ycvk = yang_cvec_get(yc); /* Check that those are proper index keys */ cvi = NULL; + if (cvk == NULL){ /* If list and no keys, all should match */ + revert++; + break; + } i = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { if ((kname = cv_name_get(cvi)) == NULL){ @@ -1313,7 +1318,7 @@ xml_find_index_yang(cxobj *xp, goto done; } /* Parameter in cvk is not key or not in right key order, then we cannot call - * xml_find_keys and we need to revert to noynag + * xml_find_keys and we need to revert to noyang */ if ((ycv = cvec_i(ycvk, i)) == NULL || strcmp(kname, cv_string_get(ycv))){ revert++; @@ -1411,12 +1416,11 @@ xml_find_index_yang(cxobj *xp, * - Otherwise search is made using linear search * * @param[in] xp Parent xml node. - * @param[in] yc Yang spec of list child (preferred) See rule (2) above * @param[in] yp Yang spec of parent node or yang-spec/yang (Alternative if yc not given). * @param[in] namespace Namespace (needed only if name is derived from top-symbol) - * @param[in] name Name of child (not required if yc given) + * @param[in] name Name of child * @param[in] cvk List of keys and values as CLIgen vector on the form k1=foo, k2=bar - * @param[out] xvec Array of found nodes + * @param[out] xvec Array of result nodes. Must be initialized on entry * @retval 0 OK, see xret * @retval -1 Error * @code @@ -1424,10 +1428,12 @@ xml_find_index_yang(cxobj *xp, * cvec *cvk = NULL; vector of index keys * cxobj *x; * ... Populate cvk with key/values eg a:5 b:6 - * if (clixon_xml_find_index(xp, yp, NULL, "a", ns, cvk, &xv) < 0) + * if ((xv = clixon_xvec_new()) == NULL) * err; - * for (i=0; i[=] */ static int -xpath_list_optimize_fn(xpath_tree *xt, - cxobj *xv, - clixon_xvec **xvec) +xpath_list_optimize_fn(xpath_tree *xt, + cxobj *xv, + clixon_xvec *xvec) { int retval = -1; xpath_tree *xm = NULL; @@ -329,8 +329,10 @@ xpath_optimize_check(xpath_tree *xs, if (!_optimize_enable) return 0; /* use regular code */ + if ((xvec = clixon_xvec_new()) == NULL) + return -1; /* Glue code since xpath code uses (old) cxobj ** and search code uses (new) clixon_xvec */ - if ((ret = xpath_list_optimize_fn(xs, xv, &xvec)) < 0) + if ((ret = xpath_list_optimize_fn(xs, xv, xvec)) < 0) return -1; if (ret == 1){ if (clixon_xvec_extract(xvec, xvec0, xlen0) < 0) diff --git a/test/test_api.sh b/test/test_api.sh index 066f0437..127bccbd 100755 --- a/test/test_api.sh +++ b/test/test_api.sh @@ -156,8 +156,10 @@ trigger_rpc(clicon_handle h, /* Clicon handle */ goto done; cv_name_set(cv, "k"); cv_string_set(cv, "5"); + if ((xv = clixon_xvec_new()) == NULL) + goto done; /* Use form 2c use spec of xc + name */ - if (clixon_xml_find_index(xc, NULL, NULL, "y3", cvk, &xv) < 0) + if (clixon_xml_find_index(xc, NULL, NULL, "y3", cvk, xv) < 0) goto done; if (clixon_xvec_len(xv)) val = xml_find_body(clixon_xvec_i(xv,0), "val"); diff --git a/test/test_instance_id_multi.sh b/test/test_instance_id_multi.sh new file mode 100755 index 00000000..c668c186 --- /dev/null +++ b/test/test_instance_id_multi.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# Instance-id tests with multiple results +# RFC 7950 Sections 9.13 and 14 says that: +# Predicates are used only for specifying the values for the key nodes for list entries +# but does not explicitly say that list nodes can skip them +# And in RFC8341 node-instance-identifiers are defined as: +# All the same rules as an instance-identifier apply, except that predicates for keys are optional. +# If a key predicate is missing, then the node-instance-identifier represents all possible server +# instances for that key. + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +: ${clixon_util_path:=clixon_util_path -D $DBG} + +xml1=$dir/xml1.xml +ydir=$dir/yang + +if [ ! -d $ydir ]; then + mkdir $ydir +fi + +# Nested lists +cat < $ydir/example.yang +module example{ + yang-version 1.1; + namespace "urn:example:id"; + prefix ex; + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + container next{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } + } + } +} +EOF + +cat < $xml1 + + + a + + + a + 11 + + + b + 22 + + + + + b + + + a + 33 + + + +
+EOF + +new "instance-id top-level param" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter)" 0 "0: aa11b22" "1: ba33" + +new "instance-id a/next" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter[ex:name=\"a\"]/ex:next)" 0 "0: a11b22" + +new "instance-id a/next/param" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter[ex:name=\"a\"]/ex:next/ex:parameter)" 0 "0: a11" "1: b22" + +new "instance-id a/next/param/a" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter[ex:name=\"a\"]/ex:next/ex:parameter[ex:name=\"a\"])" 0 "0: a11" "0: a11" + +new "instance-id a/next/param/a/value" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter[ex:name=\"a\"]/ex:next/ex:parameter[ex:name=\"a\"]/ex:value)" 0 "0: 11" + +new "instance-id top-level parameter/next" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter/ex:next)" 0 "0: a11b22" "1: a33" + +new "instance-id top-level parameter/next/parameter" +expectpart "$($clixon_util_path -f $xml1 -y $ydir -p /ex:table/ex:parameter/ex:next/ex:parameter)" 0 "0: a11" "1: b22" "2: a33" + +rm -rf $dir +unset clixon_util_path # for other script reusing it + + diff --git a/test/test_nacm_datanode_write.sh b/test/test_nacm_datanode_write.sh new file mode 100755 index 00000000..d351539c --- /dev/null +++ b/test/test_nacm_datanode_write.sh @@ -0,0 +1,296 @@ +#!/usr/bin/env bash +# Authentication and authorization and IETF NACM +# NACM data node rules +# The RFC 8341 examples in the appendix are very limited. +# Here focus on datanode paths from a write perspective. +# Especially a list in a list to test vector rules +# The test uses a nested list and makes CRUD operations on one object "b" + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +# Common NACM scripts +. ./nacm.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/nacm-example.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + false + internal + none + +EOF + +cat < $fyang +module nacm-example{ + yang-version 1.1; + namespace "urn:example:nacm"; + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + container table{ + list parameter{ + key name; + leaf name{ + type string; + } + container next{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } + } + } +} +EOF + +RULES=$(cat < + false + deny + deny + permit + + $NGROUPS + + + limited-acl + limited + + + value + nacm-example + create delete update + /ex:table/ex:parameter/ex:next/ex:parameter/ex:value + permit + + + parameter + nacm-example + read update + /ex:table/ex:parameter/ex:next/ex:parameter + permit + + + + + $NADMIN + + +EOF +) + +CONFIG=$(cat < + + a + + + a + 72 + + + + + b + + + a + 99 + + + + +EOF +) + +# +# Arguments, permit/deny on different levels: +# Configs (permit/deny): +# - write-default +# - param access +# - param action +# - value access +# - value action +# Tests, epxect true/false: +# - create +# - read +# - update +# - delete +testrun(){ + writedefault=$1 + paramaccess=$2 + paramaction=$3 + valueaccess=$4 + valueaction=$5 + testc=$6 + testr=$7 + testu=$8 + testd=$9 + + new "set write-default $writedefault" + expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-netconf-acm:nacm/write-default -d "{\"ietf-netconf-acm:write-default\":\"$writedefault\"}" )" 0 "HTTP/1.1 204 No Content" + + new "set param rule access: $paramaccess" + expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=parameter/access-operations -d "{\"ietf-netconf-acm:access-operations\":\"$paramaccess\"}" )" 0 "HTTP/1.1 204 No Content" + + new "set param rule access: $paramaction" + expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=parameter/action -d "{\"ietf-netconf-acm:action\":\"$paramaction\"}" )" 0 "HTTP/1.1 204 No Content" + + new "set value rule access: $valueaccess" + expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=value/access-operations -d "{\"ietf-netconf-acm:access-operations\":\"$valueaccess\"}" )" 0 "HTTP/1.1 204 No Content" + + new "set value rule access: $valueaction" + expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl/rule=value/action -d "{\"ietf-netconf-acm:action\":\"$valueaction\"}" )" 0 "HTTP/1.1 204 No Content" + +#--------------- Here tests: create/update/read/delete + + new "create object b" + if $testc; then + expectpart "$(curl -u wilma:bar -siS -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/nacm-example:table/parameter=a/next -d '{"nacm-example:parameter":[{"name":"b","value":"17"}]}')" 0 'HTTP/1.1 201 Created' + else + expectpart "$(curl -u wilma:bar -siS -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/nacm-example:table/parameter=a/next -d '{"nacm-example:parameter":[{"name":"b","value":"17"}]}')" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' + fi + new "read object b" + if $testr; then + expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"b","value":"17"}\]}' + else + expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b)" 0 'HTTP/1.1 404 Not Found' + fi + + new "update object b" + if $testu; then + expectpart "$(curl -u wilma:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b -d '{"nacm-example:parameter":[{"name":"b","value":"92"}]}')" 0 'HTTP/1.1 204 No Content' + else + expectpart "$(curl -u wilma:bar -siS -X PUT -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b -d '{"nacm-example:parameter":[{"name":"b","value":"92"}]}')" 0 'HTTP/1.1 403 Forbidden' + # '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' + fi + + new "delete object b" + if $testd; then + expectpart "$(curl -u wilma:bar -siS -X DELETE http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b)" 0 'HTTP/1.1 204 No Content' + else # XXX can vara olika + ret=$(curl -u wilma:bar -siS -X DELETE http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b) + r=$? + if [ $r != 0 ]; then + err "retval: $r" "0" + fi + match1=$(echo "$ret" | grep --null -o 'HTTP/1.1 403 Forbidden') + r1=$? + match2=$(echo "$ret" | grep --null -o 'HTTP/1.1 409 Conflict') + r2=$? + if [ $r1 != 0 -a $r2 != 0 ]; then + err "'HTTP/1.1 403 Forbidden' or 'HTTP/1.1 409 Conflict'" "$ret" + fi + # Ensure delete + new "ensure delete object b" + expectpart "$(curl -u andy:bar -siS -X DELETE http://localhost/restconf/data/nacm-example:table/parameter=a/next/parameter=b)" 0 'HTTP/1.1' # ignore error + fi + +} # testrun + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg +fi + +new "waiting" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + sudo pkill -u $wwwuser -f clixon_restconf + + new "start restconf daemon (-a is enable basic authentication)" + start_restconf -f $cfg -- -a + + new "waiting" + wait_restconf +fi + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$" + +new "set app config" +expecteof "$clixon_netconf -qf $cfg" 0 "$CONFIG]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + +new "enable nacm" +expectpart "$(curl -u andy:bar -siS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content" + +#--------------- nacm enabled +# config: def param:access/action value:access/action +# test: create read update delete +# default deny +testrun permit "*" permit "*" permit true true true true +testrun permit "*" deny "*" deny false false false false +testrun permit "*" permit "*" deny false false false false +testrun permit "*" permit "update delete" deny true true false false +testrun permit "*" permit "delete" deny true true true false +testrun permit "delete" deny "*" permit true true true false +testrun permit "update" deny "*" permit true true false true +testrun permit "read" deny "*" permit true false true true + +testrun deny "*" permit "*" permit true true true true +testrun deny "*" permit "*" deny false false false false +testrun deny "create" permit "*" permit true true false false +# strange: a read permit on a sub-object while default read deny opens up all +testrun deny "create read" permit "*" permit true true false false +testrun deny "create" permit "create" permit true false false false +testrun deny "create update" permit "create update" permit true false true false +testrun deny "create update delete" permit "create update delete" permit true false true true +testrun deny "create update delete" permit "update" deny true false false true +testrun deny "create update delete" permit "delete" deny true false true false +# OK but only gives sub-.object (not value) too complex to test +# testrun deny "create update read" permit "read" deny true true true false +testrun deny "*" deny "*" deny false false false false + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi +if [ $BE -ne 0 ]; then # Bring your own backend + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir diff --git a/test/test_xml_trees.sh b/test/test_xml_trees.sh new file mode 100755 index 00000000..47122597 --- /dev/null +++ b/test/test_xml_trees.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# XML inserty and merge two trees +# This is mainly a development API check + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +: ${clixon_util_xml_mod:=clixon_util_xml_mod} + +OPTS="-D $DBG" + +APPNAME=example + +cfg=$dir/conf_yang.xml +fyang=$dir/example.yang + +cat < $cfg + + $cfg + $dir + /usr/local/share/clixon + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + +EOF + +cat < $fyang +module example { + yang-version 1.1; + namespace "urn:example:example"; + prefix ex; + revision 2019-01-13; + container c{ + leaf d{ + type int32; + } + list a{ + key x; + leaf x{ + type int32; + } + } + } +} +EOF + +# insert/merge a tree into a base tree +# Args: +# 1: operation +# 2: base tree +# 3: insert tree +# 4: xpath +# 5: retval +# 6: result +testrun(){ + op=$1 + x0=$2 + x1=$3 + xp=$4 + ret=$5 + res=$6 + + echo "$clixon_util_xml_mod -o $op -y $fyang -b "$x0" -x "$x1" -p $xp $OPTS" + expectpart "$($clixon_util_xml_mod -o $op -y $fyang -b "$x0" -x "$x1" -p $xp $OPTS)" $ret "$res" +} + +new "test params: -y $fyang $OPTS" + +# -------- insert +# Empty element base list +x0a='' +x0b='' +p=c + +new "insert 1st element" +testrun insert "$x0a$x0b" "$x0a1$x0b" $p 0 '1' + +new "insert 2nd element" +testrun insert "$x0a2$x0b" "$x0a1$x0b" $p 0 '12' + +new "insert container" +testrun insert "$x0a1$x0b" "$x0a42$x0b" c 0 '421' + +# -------- parse parent +new "parent 1st element" +testrun parent "$x0a$x0b" "1" $p 0 '1' + +new "patrse parent element" +testrun parent "$x0a2$x0b" '1' $p 0 '12' + + +new "parse parent container" +testrun parent "$x0a1$x0b" '42' c 0 '421' + +# -------- merge + +new "merge empty" +testrun merge "$x0a$x0b" "$x0a$x0b" $p 0 '' + +new "merge single w empty" +testrun merge "$x0a1$x0b" "$x0a$x0b" . 0 '1' + +new "merge empty w single" +testrun merge "$x0a$x0b" "$x0a1$x0b" . 0 '1' + +new "merge equal single" +testrun merge "$x0a1$x0b" "$x0a1$x0b" . 0 '1' + +new "merge overlap" +testrun merge "$x0a12$x0b" "$x0a23$x0b" . 0 '123' + +new "merge list and leaf" +testrun merge "$x0a12$x0b" "$x0a42$x0b" . 0 '4212' + +new "merge leaf and list" +testrun merge "$x0a42$x0b" "$x0a12$x0b" . 0 '4212' + +new "merge overlap with path fail, merge does not work w subtrees" +testrun merge "$x0a12$x0b" "$x0a23$x0b" c 255 '' + +rm -rf $dir + +# unset conditional parameters +unset clixon_util_xml_mod diff --git a/util/clixon_util_stream.c b/util/clixon_util_stream.c index 526c6058..48dbecf7 100644 --- a/util/clixon_util_stream.c +++ b/util/clixon_util_stream.c @@ -70,10 +70,10 @@ struct curlbuf{ * realloc. Therefore, we append new data to the userdata buffer. */ static size_t -curl_get_cb(void *ptr, +curl_get_cb(void *ptr, size_t size, size_t nmemb, - void *userdata) + void *userdata) { struct curlbuf *buf = (struct curlbuf *)userdata; int len;