diff --git a/CHANGELOG.md b/CHANGELOG.md index 6034a570..93dad3ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ ## 4.5.0 Expected: May 2020 +### Major New features + +* NACM RFC341 datanode paths + * Read operations now implemented + * Create/Write/Update not yet + ### API changes on existing protocol/config features (You may have have to change how you use Clixon) * Stricter incoming RPC sanity checking, error messages may have changed. @@ -36,6 +42,7 @@ Expected: May 2020 ### Minor changes +* Sanity check of duplicates prefixes in Yang modules and submodules as defined in RFC 7950 Sec 7.1.4 ## 4.4.0 5 April 2020 @@ -49,7 +56,7 @@ features include optimized search functions and a repair callback. * New "general-purpose" datastore upgrade/repair callback called once on startup, intended for low-level general upgrades and as a complement to module-specific upgrade. * Called on startup after initial XML parsing, but before module-specific upgrades - * Enabled by definign the `.ca_datastore_upgrade` + * Enabled by defining the `.ca_datastore_upgrade` * [General-purpose upgrade documentation](https://clixon-docs.readthedocs.io/en/latest/backend.html#general-purpose) * New and updated search functions using xpath, api-path and instance-id, and explicit indexes * New search functions using api-path and instance_id: diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 0795cfc3..566e26e9 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -474,7 +474,7 @@ client_get_config_only(clicon_handle h, if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* NACM datanode/module read validation */ - if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0) + if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) goto done; } cprintf(cbret, ""); @@ -1174,7 +1174,7 @@ from_client_get(clicon_handle h, if (xpath_vec(xret, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) goto done; /* NACM datanode/module read validation */ - if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0) + if (nacm_datanode_read(h, xret, xvec, xlen, username, xnacm) < 0) goto done; } cprintf(cbret, ""); /* OK */ diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 38bd3624..04421b88 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -130,6 +130,7 @@ clixon_plugin_statedata(clicon_handle h, if (debug) clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__); #endif + /* XXX: ret == 0 invalid yang binding should be handled as internal error */ if (xml_bind_yang(x, YB_MODULE, yspec, NULL) < 0) goto done; if (xml_apply(x, CX_ELMNT, xml_sort, h) < 0) diff --git a/apps/netconf/netconf_filter.c b/apps/netconf/netconf_filter.c index 740b95c5..b273bfcc 100644 --- a/apps/netconf/netconf_filter.c +++ b/apps/netconf/netconf_filter.c @@ -95,7 +95,7 @@ leafstring(cxobj *x) return xml_value(c); } -/*! Internal recursive part where configuration xml tree is pruned frim filter +/*! Internal recursive part where configuration xml tree is pruned from filter * assume parent has been selected and filter match (same name) as parent * parent is pruned according to selection. * @param[in] xfilter Filter xml diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index bfda75c0..38ac9ef9 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -738,9 +738,11 @@ restconf_insert_attributes(cxobj *xdata, } /* Add prefix/namespaces used in attributes */ cv = NULL; - while ((cv = cvec_each(nsc, cv)) != NULL) - if (xmlns_set(xdata, cv_name_get(cv), cv_string_get(cv)) < 0) + while ((cv = cvec_each(nsc, cv)) != NULL){ + char *ns = cv_string_get(cv); + if (xmlns_set(xdata, cv_name_get(cv), ns) < 0) goto done; + } if (nsc) xml_sort(xdata, NULL); /* Ensure attr is first */ cprintf(cb, "/>"); diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h index 4589585e..9b74372a 100644 --- a/lib/clixon/clixon_nacm.h +++ b/lib/clixon/clixon_nacm.h @@ -57,8 +57,9 @@ enum nacm_access{ * Prototypes */ int nacm_rpc(char *rpc, char *module, char *username, cxobj *xnacm, cbuf *cbret); -int nacm_datanode_read(cxobj *xt, cxobj **xvec, size_t xlen, char *username, cxobj *nacm_xtree); -int nacm_datanode_write(cxobj *xt, cxobj *xr, enum nacm_access access, +int nacm_datanode_read(clicon_handle h, cxobj *xt, cxobj **xvec, size_t xlen, char *username, + cxobj *nacm_xtree); +int nacm_datanode_write(clicon_handle h, cxobj *xt, cxobj *xr, enum nacm_access access, char *username, cxobj *xnacm, cbuf *cbret); int nacm_access_pre(clicon_handle h, char *username, cxobj **xnacmp); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index dcabb69f..5aa19999 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -64,7 +64,8 @@ int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); int xml2xpath(cxobj *x, char **xpath); -int assign_namespaces(cxobj *x0, cxobj *x1, cxobj *x1p); +int assign_namespace_element(cxobj *x0, cxobj *x1, cxobj *x1p); +int assign_namespace_body(cxobj *x0, char *x0bstr, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/clixon/clixon_xml_nsctx.h b/lib/clixon/clixon_xml_nsctx.h index 740ba078..54efd977 100644 --- a/lib/clixon/clixon_xml_nsctx.h +++ b/lib/clixon/clixon_xml_nsctx.h @@ -57,8 +57,8 @@ int xml_nsctx_node(cxobj *x, cvec **ncp); int xml_nsctx_yang(yang_stmt *yn, cvec **ncp); int xml_nsctx_yangspec(yang_stmt *yspec, cvec **ncp); -int xml2ns(cxobj *x, char *localname, char **namespace); -int xml2prefix(cxobj *xn, char *namespace, char **prefixp); -int xml_localname_check(cxobj *xn, void *arg); +int xml2ns(cxobj *x, char *localname, char **namespace); +int xml2prefix(cxobj *xn, char *namespace, char **prefixp); +int xml_localname_check(cxobj *xn, void *arg); #endif /* _CLIXON_XML_NSCTX_H */ diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 94bb3550..bce8bf5b 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -131,15 +131,19 @@ attr_ns_value(cxobj *x, goto done; } +/*! When new body is added, some needs type lookup is made and namespace checked + * This includes identityrefs, paths + * This code identifies x0 as an identityref, looks at the _body_ string and ensures the right + * namespace is inserted in x1. + */ static int -check_identityref(cxobj *x0, - cxobj *x1, - cxobj *x1p, - char *x1bstr, - yang_stmt *y) +check_body_namespace(cxobj *x0, + cxobj *x1, + cxobj *x1p, + char *x1bstr, + yang_stmt *y) { int retval = -1; - yang_stmt *yrestype = NULL; char *prefix = NULL; char *ns0 = NULL; char *ns1 = NULL; @@ -151,11 +155,6 @@ check_identityref(cxobj *x0, isroot = xml_parent(x1p)==NULL && strcmp(xml_name(x1p), "config") == 0 && xml_prefix(x1p)==NULL; - if (yang_type_get(y, NULL, &yrestype, - NULL, NULL, NULL, NULL, NULL) < 0) - goto done; - if (strcmp(yang_argument_get(yrestype), "identityref") != 0) - goto ok; /* skip */ if (nodeid_split(x1bstr, &prefix, NULL) < 0) goto done; if (prefix == NULL) @@ -240,12 +239,12 @@ text_modify(clicon_handle h, char *opstr = NULL; char *x1name; char *x1cname; /* child name */ - cxobj *x0c; /* base child */ - cxobj *x0b; /* base body */ - cxobj *x1c; /* mod child */ - char *x0bstr; /* mod body string */ - char *x1bstr; /* mod body string */ - yang_stmt *yc; /* yang child */ + cxobj *x0c; /* base child */ + cxobj *x0b; /* base body */ + cxobj *x1c; /* mod child */ + char *x0bstr; /* mod body string */ + char *x1bstr; /* mod body string */ + yang_stmt *yc; /* yang child */ cxobj **x0vec = NULL; int i; int ret; @@ -318,7 +317,7 @@ text_modify(clicon_handle h, * of ordered-by user and (changed) insert attribute. */ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -335,7 +334,7 @@ text_modify(clicon_handle h, case OP_NONE: /* fall thru */ if (x0==NULL){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -350,8 +349,9 @@ text_modify(clicon_handle h, /* Get namespace from x1 * Check if namespace exists in x0 parent * if not add new binding and replace in x0. + * See also xmlns copying of attributes in the body section below */ - if (assign_namespaces(x1, x0, x0p) < 0) + if (assign_namespace_element(x1, x0, x0p) < 0) goto done; changed++; if (op==OP_NONE) @@ -362,17 +362,46 @@ text_modify(clicon_handle h, } } if (x1bstr){ - /* XXX: Do the type lookup here inline instead, there are more - * cases where we check for types, eg where we now call clixon_trim2 - * AND in nacm path where we need proper namespace contexts. + /* Some bodies (eg identityref) requires proper namespace setup, so a type lookup is + * necessary. */ - if (check_identityref(x1, x0, x0p, x1bstr, y0) < 0) + yang_stmt *yrestype = NULL; + char *restype; + + if (yang_type_get(y0, NULL, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) goto done; + if (yrestype == NULL){ + clicon_err(OE_CFG, EFAULT, "No restype (internal error)"); + goto done; + } + restype = yang_argument_get(yrestype); + if (strcmp(restype, "identityref") == 0){ + x1bstr = clixon_trim2(x1bstr, " \t\n"); + if (check_body_namespace(x1, x0, x0p, x1bstr, y0) < 0) + goto done; + } + else{ + /* Some bodies strip pretty-printed here, unsure where to do this,.. */ + if (strcmp(restype, "enumeration") == 0 || + strcmp(restype, "bits") == 0) + x1bstr = clixon_trim2(x1bstr, " \t\n"); + + /* If origin body has namespace definitions, copy them. The reason is that + * some bodies rely on namespace prefixes, such as NACM path, but there is + * no way we can now this here. + * However, this may lead to namespace collisions if these prefixes are not + * canonical, and may collide with the assign_namespace_element() above (but that + * is for element sysmbols) + * Oh well. + */ + if (assign_namespace_body(x1, x1bstr, x0) < 0) + goto done; + } if ((x0b = xml_body_get(x0)) != NULL){ x0bstr = xml_value(x0b); if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, + if ((ret = nacm_datanode_write(h, NULL, x1, x0bstr==NULL?NACM_CREATE:NACM_UPDATE, username, xnacm, cbret)) < 0) goto done; @@ -398,7 +427,7 @@ text_modify(clicon_handle h, case OP_REMOVE: /* fall thru */ if (x0){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -460,7 +489,7 @@ text_modify(clicon_handle h, * of ordered-by user and (changed) insert attribute. */ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -486,7 +515,7 @@ text_modify(clicon_handle h, if (op == OP_NONE) break; if (op==OP_MERGE && !permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -503,7 +532,7 @@ text_modify(clicon_handle h, } /* anyxml, anydata */ if (x0==NULL){ if (op==OP_MERGE && !permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -522,7 +551,7 @@ text_modify(clicon_handle h, * Check if namespace exists in x0 parent * if not add new binding and replace in x0. */ - if (assign_namespaces(x1, x0, x0p) < 0) + if (assign_namespace_element(x1, x0, x0p) < 0) goto done; if (op==OP_NONE) xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ @@ -595,7 +624,7 @@ text_modify(clicon_handle h, case OP_REMOVE: /* fall thru */ if (x0){ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -676,7 +705,7 @@ text_modify_top(clicon_handle h, case OP_REMOVE: case OP_REPLACE: if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -710,7 +739,7 @@ text_modify_top(clicon_handle h, /* Special case top-level replace */ else if (op == OP_REPLACE || op == OP_DELETE){ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index 6a0a18da..665d0f35 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -69,6 +69,8 @@ #include "clixon_yang_module.h" #include "clixon_datastore.h" #include "clixon_xml_nsctx.h" +#include "clixon_xml_map.h" +#include "clixon_path.h" #include "clixon_nacm.h" /* NACM namespace for use with xml namespace contexts and xpath */ @@ -311,38 +313,131 @@ nacm_rpc(char *rpc, * Datanode/module read and write */ -/*! We have a rule matching user group. Now match proper write operation and module +/*! There is a data-node rule with a path. Match it. + * @param[in] h Clixon handle + * @param[in] xt XML root tree with "config" label. + * @param[in] xr XML requestor node (part of xt) + * @param[in] pathobj XML nacm path node * @retval -1 Error * @retval 0 No Match * @retval 1 Match - * @see RFC8341 3.4.5. Data Node Access Validation point (6) + * @retval 1 SubMatch + + o For HEAD and GET requests, any data nodes that are ancestor nodes + of the target resource are considered to be part of the retrieval + request for access control purposes. + Data nodes to which the client does not have read access are silently + omitted, along with any descendants, +the selection criteria are applied to the subset of nodes that the user + is authorized to read, not the entire datastore. + * Assume tree: + * tree + * | + * a + * / \ + * b c + * access: / + * rule1: /a/b deny + * rule2: / permit + * returned tree: + * tree + * | + * a + * \ + * c + * Algorithm: + * Traverse all rules and access the tree: + * rule1: purge b + * rule2: accept rest. */ static int -nacm_rule_datanode(cxobj *xt, - cxobj *xr, +nacm_rule_datanode_path(clicon_handle h, + cxobj *xt, + cxobj *xreq, + cxobj *pathobj) +{ + int retval = -1; + cvec *nsc0 = NULL; /* Non-canonical namespace context */ + cxobj *xp; /* Node specified by path */ + cxobj *xa; /* Ancestor */ + char *path0; /* Non-canonical path */ + char *path; /* Canonical path */ + yang_stmt *yspec; + cxobj **xvec = NULL; + size_t xlen = 0; + int ret; + + yspec = clicon_dbspec_yang(h); + path0 = clixon_trim2(xml_body(pathobj), " \t\n"); + /* Create namespace context for with nacm namespace as default */ + if (xml_nsctx_node(pathobj, &nsc0) < 0) + goto done; + /* instance-id requires canonical paths */ + if (xpath2canonical(path0, nsc0, yspec, &path, NULL) < 0) + goto done; + if ((ret = clixon_xml_find_instance_id(xt, yspec, &xvec, &xlen, "%s", path)) < 0) + goto nomatch; + if (ret == 0) + goto nomatch; + assert(xlen == 1); /* XXX: This may be rich */ + /* xp is the node specified by the path */ + xp = xvec[0]; + + /* The requested node (xreq) is the node specified by the path (xp) or is a + * descendant node of the path xn. + * By checking if xreq is equal to xp or if any of its ancestors is = + * xmatch is one of xvec[] or an ancestor of the xvec[] nodes. + */ + xa = xreq; + do { + if (xp == xa) + goto match; + } while ((xa = xml_parent(xa)) != NULL); + match: + retval = 1; + done: + if (xvec) + free(xvec); + return retval; + nomatch: + retval = 0; + goto done; +} + +/*! We have a rule matching user group. Now match proper write operation and module + * @param[in] h Clixon handle + * @param[in] xt XML root tree with "config" label. + * @param[in] xreq XML requestor node (part of xt) + * @param[in] xrule XML rule node + * @param[in] access read/write/create/update/delete/exec + * @retval -1 Error + * @retval 0 No Match + * @retval 1 Match + * @see RFC8341 3.4.5. Data Node Access Validation point (6) + * @note read access is not handled in this function, see nacm_data_read_xrule + */ +static int +nacm_rule_datanode(clicon_handle h, + cxobj *xt, + cxobj *xreq, cxobj *xrule, enum nacm_access access) { int retval = -1; - char *path; char *access_operations; char *module_rule; /* rule module name */ yang_stmt *ys; yang_stmt *ymod; char *module; - cxobj *xpath; /* xpath match */ - cxobj *xp; /* parent */ - cvec *nsc = NULL; + cxobj *pathobj; + int ret; - /* Create namespace context for with nacm namespace as default */ - if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL) - goto done; /* 6a) The rule's "module-name" leaf is "*" or equals the name of * the YANG module where the requested data node is defined. */ if ((module_rule = xml_find_body(xrule, "module-name")) == NULL) goto nomatch; if (strcmp(module_rule,"*")!=0){ - if (xr==NULL || (ys = xml_spec(xr)) == NULL) + if (xreq==NULL || (ys = xml_spec(xreq)) == NULL) goto nomatch; ymod = ys_module(ys); module = yang_argument_get(ymod); @@ -353,15 +448,13 @@ nacm_rule_datanode(cxobj *xt, /* 6b) Either (1) the rule does not have a "rule-type" defined or (2) the "rule-type" is "data-node" and the "path" matches the requested data node, action node, or notification node. */ - if ((path = xml_find_body(xrule, "path")) == NULL){ + if ((pathobj = xml_find_type(xrule, NULL, "path", CX_ELMNT)) == NULL){ if (xml_find_body(xrule, "rpc-name") ||xml_find_body(xrule, "notification-name")) goto nomatch; } - else - path = clixon_trim2(path, " \t\n"); access_operations = xml_find_body(xrule, "access-operations"); switch (access){ - case NACM_READ: + case NACM_READ: /* Not used, see nacm_data_read_xrule */ /* 6c) For a "read" access operation, the rule's "access-operations" leaf has the "read" bit set or has the special value "*" */ if (!match_access(access_operations, "read", NULL)) @@ -388,108 +481,166 @@ nacm_rule_datanode(cxobj *xt, default: break; } - /* Here module is matched, now check for path if any NYI - * A path is considered to match if the requested node is the node + /* Here module is matched, now check for path if any + * A path is considered to match if the requested node (xreq) is the node * specified by the path or is a descendant node of the path */ - if (path){ -#if 0 - if ((ret = clixon_xml_find_instance_id(xt, yt, xvec, xlen, "%s", path)) == NULL) + if (pathobj){ + if ((ret = nacm_rule_datanode_path(h, xt, xreq, pathobj)) < 0) + goto done; + if (ret == 0) goto nomatch; -#else - if ((xpath = xpath_first(xt, nsc, "%s", path)) == NULL) - goto nomatch; -#endif - /* The requested node xr is the node specified by the path or is a - * descendant node of the path: - * xmatch is one of xvec[] or an ancestor of the xvec[] nodes. - */ - xp = xr; - do { - if (xpath == xp) - goto match; - } while ((xp = xml_parent(xp)) != NULL); + if (ret == 2) + goto submatch; } - match: + // match: retval = 1; done: - if (nsc) - xml_nsctx_free(nsc); return retval; nomatch: retval = 0; goto done; + submatch: + retval = 2; + goto done; } -/*! Go through all rules for a requested node - * @param[in] xt XML root tree with "config" label - * @param[in] xr Requested node (node in xt) - * @param[in] gvec NACM groups where user is member - * @param[in] glen Length of gvec - * @param[in] rlistvec NACM rule-list entries - * @param[in] rlistlen Length of rlistvec - * @param[in] nsc NACM namespace context for xpaths - * @param[out] xrulep If set, then points to matching rule - */ static int -nacm_data_read_xr(cxobj *xt, - cxobj *xr, - cxobj **gvec, - size_t glen, - cxobj **rlistvec, - size_t rlistlen, - cvec *nsc, - cxobj **xrulep) +nacm_data_read_action(cxobj *xrule, + cxobj *xp, + int default_permit) { - int retval = -1; - int i, j; - cxobj *rlist; - char *gname; - cxobj **rvec = NULL; /* rules */ - size_t rlen; - cxobj *xrule = NULL; - int match = 0; + int retval = -1; + char *action; - for (i=0; i 0) + matches++; } - else{ /* no rule matching xr, apply default */ - /*11. For a "read" access operation, if the "read-default" leaf is set - to "permit", then include the requested data node in the reply; - otherwise, do not include the requested data node or any of its - descendants in the reply.*/ - if (strcmp(read_default, "deny")==0) - if (xml_purge(xr) < 0) - goto done; - } - } /* xr */ + if (rvec){ + free(rvec); + rvec=NULL; + } + } + /* If there were accpet rules in some matches, remove everything else */ + if (matches && strcmp(read_default, "deny") == 0) + if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + if (strcmp(read_default, "deny") == 0){ + if (matches == 0) + goto step9; /* delete all */ + else /* Accept rules that override default delete: delete everything not marked) */ + if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0) + goto done; + } + /* reset flag */ + if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + goto done; goto ok; /* 8. At this point, no matching rule was found in any rule-list entry. */ @@ -670,9 +840,10 @@ nacm_datanode_read(cxobj *xt, /*! Make nacm datanode and module rule write access validation * The operations of NACM are: create, read, update, delete, exec * where write is short-hand for create+delete+update - * @param[in] xt XML root tree with "config" label. XXX? - * @param[in] xr XML requestor node (part of xt) - * @param[in] op NACM access of xr + * @param[in] h Clixon handle + * @param[in] xt XML root tree with "config" label. + * @param[in] xreq XML requestor node (part of xt) + * @param[in] op NACM access of xreq * @param[in] username User making access * @param[in] xnacm NACM xml tree * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. @@ -684,14 +855,15 @@ nacm_datanode_read(cxobj *xt, * @see nacm_rpc */ int -nacm_datanode_write(cxobj *xt, - cxobj *xr, +nacm_datanode_write(clicon_handle h, + cxobj *xt, + cxobj *xreq, enum nacm_access access, char *username, cxobj *xnacm, cbuf *cbret) { - int retval = -1; + int retval = -1; cxobj **gvec = NULL; /* groups */ size_t glen; cxobj **rlistvec = NULL; /* rule-list */ @@ -749,7 +921,7 @@ nacm_datanode_write(cxobj *xt, */ for (j=0; jcp_id)) == NULL){ - clicon_err(OE_YANG, ENOENT, "Corresponding yang node for id not found"); + clicon_err(OE_YANG, ENOENT, "Corresponding yang node for id:%s not found", + cp->cp_id); goto fail; } cp->cp_yang = yc; @@ -1411,9 +1415,9 @@ instance_id_resolve(clixon_path *cplist, * @retval 1 OK with found xml nodes in xvec (if any) */ static int -clixon_path_search(cxobj *xt, - yang_stmt *yt, - clixon_path *cplist, +clixon_path_search(cxobj *xt, + yang_stmt *yt, + clixon_path *cplist, clixon_xvec **xvec0) { int retval = -1; @@ -1480,8 +1484,8 @@ clixon_path_search(cxobj *xt, * @param[out] xvec Vector of xml-trees. Vector must be free():d after use * @param[in] format Format string for api-path syntax * @retval -1 Error - * @retval 0 Nomatch - * @retval 1 OK with found xml nodes in xvec (if any) + * @retval 0 Non-fatal failure, yang bind failures, etc, + * @retval 1 OK with found xml nodes in xvec (if any) (xvec contains matches) * Reasons for nomatch (retval = 0) are: * - Modulename in api-path does not correspond to existing module * - Modulename not defined for top-level id. @@ -1572,7 +1576,7 @@ clixon_xml_find_api_path(cxobj *xt, * @param[out] xlen Returns length of vector in return value * @param[in] format Format string for api-path syntax * @retval -1 Error - * @retval 0 Nomatch + * @retval 0 Non-fatal failure, yang bind failures, etc, * @retval 1 OK with found xml nodes in xvec (if any) * Reasons for nomatch (retval = 0) are: * - Modulename in api-path does not correspond to existing module @@ -1589,7 +1593,7 @@ clixon_xml_find_api_path(cxobj *xt, * } * clixon_xvec_free(xvec); * @endcode - * @note canonical namespace contexts are used, seexpath2canonical + * @note canonical namespace contexts are used, see xpath2canonical * @see clixon_xml_find_api_path for RESTCONF api-paths * @see RFC7950 Sec 9.13 */ @@ -1639,7 +1643,7 @@ clixon_xml_find_instance_id(cxobj *xt, if (ret == 0) goto fail; /* Convert to api xvec format */ - if (clixon_xvec_extract(xv, xvec, xlen) < 0) + if (xv && clixon_xvec_extract(xv, xvec, xlen) < 0) goto done; retval = 1; done: diff --git a/lib/src/clixon_xml_bind.c b/lib/src/clixon_xml_bind.c index 29d331d7..011c033b 100644 --- a/lib/src/clixon_xml_bind.c +++ b/lib/src/clixon_xml_bind.c @@ -270,9 +270,7 @@ populate_self_top(cxobj *xt, /*! Find yang spec association of tree of XML nodes * * Populate xt:s children as top-level symbols - * This may be unnecessary if yspec is set on manual creation. Also note that for incoming or - * outgoing RPC need specialized function. maybe it can be built into the same function, but - * you dont know whether it is input or output rpc. + * This may be unnecessary if yspec is set on manual creation: x=xml_new(); xml_spec_set(x,y) * @param[in] xt XML tree node * @param[in] yspec Yang spec * @param[out] xerr Reason for failure, or NULL diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index c0f8cada..298a278d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -763,6 +763,7 @@ xml_tree_prune_flagged_sub(cxobj *xt, return retval; } #endif + /*! Prune everything that passes test * @param[in] xt XML tree with some node marked * @param[in] flag Which flag to test for @@ -799,7 +800,7 @@ xml_tree_prune_flagged(cxobj *xt, return retval; } -/*! Add prefix:namespace pair to xml node, set cache, prefix, etc +/*! Add prefix:namespace pair to xml node, set cache, etc * @param[in] x XML node whose namespace should change * @param[in] xp XML node where namespace attribute should be declared (can be same) * @param[in] prefix1 Use this prefix @@ -835,9 +836,6 @@ add_namespace(cxobj *x, goto done; xml_sort(xp, NULL); /* Ensure attr is first / XXX xml_insert? */ - /* 5. Add prefix to x, if any */ - if (prefix && xml_prefix_set(x, prefix) < 0) - goto done; retval = 0; done: return retval; @@ -882,6 +880,10 @@ xml_namespace_change(cxobj *x, xp = xml_parent(x); if (add_namespace(x, xp, prefix, namespace) < 0) goto done; + /* Add prefix to x, if any */ + if (prefix && xml_prefix_set(x, prefix) < 0) + goto done; + } ok: retval = 0; @@ -946,6 +948,9 @@ xml_default(cxobj *xt, } if (add_namespace(xc, xc, prefix, namespace) < 0) goto done; + /* Add prefix to x, if any */ + if (prefix && xml_prefix_set(xc, prefix) < 0) + goto done; } } @@ -1114,7 +1119,6 @@ xml2xpath(cxobj *x, return retval; } - /*! Check if the module tree x is in is assigned right XML namespace, assign if not * @param[in] x XML node *(0. You should probably find the XML root and apply this function to that.) @@ -1160,50 +1164,24 @@ xmlns_assign(cxobj *x) return retval; } -/*! Given a src node x0 and a target node x1, assign (optional) prefix and namespace - * @param[in] x0 Source XML tree - * @param[in] x1 Target XML tree - * 1. Find N=namespace(x0) - * 2. Detect if N is declared in x1 parent - * 3. If yes, assign prefix to x1 - * 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root) - * 5. Add prefix to x1, if any - * 6. Ensure x1 cache is updated - * @note switch use of x0 and x1 compared to datastore text_modify - * @see xml2ns - * XXX: fail handling: if (netconf_data_missing(cbret, NULL, "Data does not exist; cannot delete resource") < 0) - goto done; +/*! Given a src element node x0 and a target node x1, assign (optional) prefix and namespace + * @see assign_namespace_element this is a subroutine */ -int -assign_namespaces(cxobj *x0, /* source */ - cxobj *x1, /* target */ - cxobj *x1p) +static int +assign_namespace(cxobj *x0, /* source */ + cxobj *x1, /* target */ + cxobj *x1p, + int isroot, + char *namespace, + char *prefix0) { int retval = -1; - char *namespace = NULL; - char *prefix0 = NULL;; char *prefix1 = NULL; - char *prefixb = NULL; /* identityref body prefix */ + char *pexist = NULL; cvec *nsc0 = NULL; cvec *nsc = NULL; - int isroot; - char *pexist = NULL; yang_stmt *y; - /* XXX: need to identify root better than hiereustics and strcmp,... */ - isroot = xml_parent(x1p)==NULL && - (strcmp(xml_name(x1p), "config") == 0 || strcmp(xml_name(x1p), "top") == 0)&& - xml_prefix(x1p)==NULL; - - /* 1. Find N=namespace(x0) */ - prefix0 = xml_prefix(x0); - if (xml2ns(x0, prefix0, &namespace) < 0) - goto done; - if (namespace == NULL){ - clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s", - prefix0?prefix0:"NULL"); - goto done; - } /* 2a. Detect if namespace is declared in x1 target parent */ if (xml2prefix(x1p, namespace, &pexist) == 1){ /* Yes, and it has prefix pexist */ @@ -1235,7 +1213,7 @@ assign_namespaces(cxobj *x0, /* source */ if (xml2prefix(x1, namespace, &pexist) == 1){ /* Yes it exists, but is it equal? */ if (clicon_strcmp(pexist, prefix0) == 0) - ; /* Equal, reuse */ + ; /* Equal, reuse */ else{ /* namespace exist, but not equal, use existing */ /* Add prefix to x1, if any */ if (pexist && xml_prefix_set(x1, pexist) < 0) @@ -1253,31 +1231,133 @@ assign_namespaces(cxobj *x0, /* source */ } } else{ + char *ptmp; if ((y = xml_spec(x0)) == NULL){ clicon_err(OE_YANG, ENOENT, "XML %s does not have yang spec", xml_name(x0)); goto done; } - if ((prefix1 = strdup(yang_find_myprefix(y))) == NULL){ + /* Find local (imported) prefix for that module namespace */ + if (yang_find_prefix_by_namespace(y, namespace, &ptmp) < 0) + goto done; + if ((prefix1 = strdup(ptmp)) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); goto done; - } + } + } } if (add_namespace(x1, x1, prefix1, namespace) < 0) goto done; + if (prefix1 && xml_prefix_set(x1, prefix1) < 0) + goto done; } ok: /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ retval = 0; done: - if (prefixb) - free(prefixb); if (prefix1) free(prefix1); return retval; } +/*! Given a src element node x0 and a target node x1, assign (optional) prefix and namespace + * @param[in] x0 Source XML tree + * @param[in] x1 Target XML tree + * @retval 0 OK + * @retval -1 OK + * 1. Find N=namespace(x0) + * 2. Detect if N is declared in x1 parent + * 3. If yes, assign prefix to x1 + * 4. If no, create new prefix/namespace binding and assign that to x1p (x1 if x1p is root) + * 5. Add prefix to x1, if any + * 6. Ensure x1 cache is updated + * @note switch use of x0 and x1 compared to datastore text_modify + * @see xml2ns +*/ +int +assign_namespace_element(cxobj *x0, /* source */ + cxobj *x1, /* target */ + cxobj *x1p) +{ + int retval = -1; + char *namespace = NULL; + char *prefix0 = NULL;; + int isroot; + + /* XXX: need to identify root better than hiereustics and strcmp,... */ + isroot = xml_parent(x1p)==NULL && + (strcmp(xml_name(x1p), "config") == 0 || strcmp(xml_name(x1p), "top") == 0)&& + xml_prefix(x1p)==NULL; + + /* 1. Find N=namespace(x0) in x0 element */ + prefix0 = xml_prefix(x0); + if (xml2ns(x0, prefix0, &namespace) < 0) + goto done; + if (namespace == NULL){ + clicon_err(OE_XML, ENOENT, "No namespace found for prefix:%s", + prefix0?prefix0:"NULL"); + goto done; + } + if (assign_namespace(x0, x1, x1p, isroot, namespace, prefix0) < 0) + goto done; + /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ + retval = 0; + done: + return retval; +} + +/* If origin body has namespace definitions, copy them. The reason is that + * some bodies rely on namespace prefixes, such as NACM path, but there is + * no way we can now this here. + * However, this may lead to namespace collisions if these prefixes are not + * canonical, and may collide with the assign_namespace_element() above (but that + * is for element sysmbols) + */ +int +assign_namespace_body(cxobj *x0, /* source */ + char *x0bstr, + cxobj *x1) /* target */ +{ + int retval = -1; + char *namespace = NULL; + char *name; + char *prefix; + char *prefix0 = NULL;; + char *pexisting = NULL;; + cxobj *xa; + + xa = NULL; + while ((xa = xml_child_each(x0, xa, CX_ATTR)) != NULL) { + prefix = xml_prefix(xa); + name = xml_name(xa); + namespace = xml_value(xa); + if ((strcmp(name, "xmlns")==0 && prefix==NULL) || + (prefix != NULL && strcmp(prefix, "xmlns")==0)){ + if (prefix == NULL) + prefix0 = NULL; + else + prefix0 = name; + if (strcmp(namespace, NETCONF_BASE_NAMESPACE) ==0 || + strcmp(namespace, YANG_XML_NAMESPACE) ==0) + continue; + /* Detect if prefix:namespace is declared already? */ + if (xml2prefix(x1, namespace, &pexisting) == 1){ + /* Yes, and it has prefix pexist */ + if (clicon_strcmp(pexisting, prefix0) ==0) + continue; + } + if (add_namespace(x1, x1, prefix0, namespace) < 0) + goto done; + } + } + /* 6. Ensure x1 cache is updated (I think it is done w xmlns_set above) */ + retval = 0; + done: + return retval; +} + + /*! Merge a base tree x0 with x1 with yang spec y * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL @@ -1334,7 +1414,7 @@ xml_merge1(cxobj *x0, /* the target */ if (xml_value_set(x0b, x1bstr) < 0) goto done; } - if (assign_namespaces(x1, x0, x0p) < 0) + if (assign_namespace_element(x1, x0, x0p) < 0) goto done; } /* if LEAF|LEAF_LIST */ else { /* eg Y_CONTAINER, Y_LIST */ @@ -1343,7 +1423,7 @@ xml_merge1(cxobj *x0, /* the target */ goto done; xml_spec_set(x0, y0); } - if (assign_namespaces(x1, x0, x0p) < 0) + if (assign_namespace_element(x1, x0, x0p) < 0) goto done; if ((second_wave = calloc(xml_child_nr(x1), sizeof(*second_wave))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); diff --git a/lib/src/clixon_xml_nsctx.c b/lib/src/clixon_xml_nsctx.c index 0c24083a..82066f5c 100644 --- a/lib/src/clixon_xml_nsctx.c +++ b/lib/src/clixon_xml_nsctx.c @@ -152,11 +152,13 @@ xml_nsctx_get_prefix(cvec *cvv, while ((cv = cvec_each(cvv, cv)) != NULL){ if ((ns = cv_string_get(cv)) != NULL && strcmp(ns, namespace) == 0){ - *prefix = cv_name_get(cv); /* can be NULL */ + if (prefix) + *prefix = cv_name_get(cv); /* can be NULL */ return 1; } } - *prefix = NULL; + if (prefix) + *prefix = NULL; return 0; } @@ -493,7 +495,6 @@ xmlns_set(cxobj *x, goto done; if (xml_prefix_set(xa, "xmlns") < 0) goto done; - } else{ /* xmlns="" */ if ((xa = xml_new("xmlns", x, CX_ATTR)) == NULL) diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 610274c6..95400f7a 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -755,12 +755,18 @@ xml_search_yang(cxobj *xp, char *indexvar, clixon_xvec *xvec) { + int retval = -1; cxobj *xa; int low = 0; int upper = xml_child_nr(xp); int sorted = 1; int yangi; + if (xp == NULL){ + clicon_err(OE_XML, EINVAL, "xp is NULL"); + goto done; + } + upper = xml_child_nr(xp); /* Assume if there are any attributes, they are first in the list, mask them by raising low to skip them */ for (low=0; lowxv_vec; *xlen = xv->xv_len; if (xv->xv_vec != NULL){ @@ -217,7 +224,9 @@ clixon_xvec_extract(clixon_xvec *xv, xv->xv_max = 0; xv->xv_vec = NULL; } - return 0; + retval = 0; + done: + return retval; } /*! Append a new xml tree to an existing xml vector last in the list diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index cf36edfd..84b49f87 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -873,7 +873,9 @@ yang_find_mynamespace(yang_stmt *ys) * @retval -1 found * @note prefix NULL is not returned, if own module, then return its prefix * @code - * char *pfx = yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix); + * char *prefix = NULL; + * if (yang_find_prefix_by_namespace(ys, "urn:example:clixon", &prefix) < 0) + * err; * @endcode */ int @@ -1103,7 +1105,7 @@ yang_key2str(int keyword) } /*! Find top data node among all modules by namespace in xml tree - * @param[in] ysp Yang specification + * @param[in] yspec Yang specification * @param[in] xt XML node * @param[out] ymod Yang module (NULL if not found) * @retval 0 OK @@ -1112,7 +1114,7 @@ yang_key2str(int keyword) * Note that xt xml symbol may belong to submodule of ymod */ int -ys_module_by_xml(yang_stmt *ysp, +ys_module_by_xml(yang_stmt *yspec, cxobj *xt, yang_stmt **ymodp) { @@ -1130,7 +1132,7 @@ ys_module_by_xml(yang_stmt *ysp, if (namespace == NULL) goto ok; /* We got the namespace, now get the module */ - ym = yang_find_module_by_namespace(ysp, namespace); + ym = yang_find_module_by_namespace(yspec, namespace); /* Set result param */ if (ymodp && ym) *ymodp = ym; @@ -1892,6 +1894,58 @@ ys_populate_unknown(clicon_handle h, return retval; } +/*! Populate modules / submodules + * @param[in] h Clicon handle + * @param[in] ys The yang statement (module/submodule) to populate. + * + * Check RFC 7950: 7.1.4: All prefixes, including the prefix for the module itself, + * MUST be unique within the module or submodule. + */ +static int +ys_populate_module_submodule(clicon_handle h, + yang_stmt *ym) +{ + int retval = -1; + yang_stmt *yp; + yang_stmt *yi; + yang_stmt *yi2; /* remaining */ + char *p0 = NULL; + char *pi; + char *pi2; /* remaining */ + + /* Modules but not submodules have prefixes */ + if ((yp = yang_find(ym, Y_PREFIX, NULL)) != NULL) + p0 = yang_argument_get(yp); + yi = NULL; + while ((yi = yn_each(ym, yi)) != NULL) { + if (yang_keyword_get(yi) != Y_IMPORT) + continue; + yp = yang_find(yi, Y_PREFIX, NULL); + pi = yang_argument_get(yp); + if (p0 && strcmp(p0, pi) == 0){ /* Check top-level */ + clicon_err(OE_YANG, EFAULT, "Prefix %s in module %s is not unique but should be (see RFC 7950 7.1.4)", + pi, yang_argument_get(ym)); + goto done; + } + /* Check rest of imports */ + yi2 = yi; + while ((yi2 = yn_each(ym, yi2)) != NULL) { + if (yang_keyword_get(yi2) != Y_IMPORT) + continue; + yp = yang_find(yi2, Y_PREFIX, NULL); + pi2 = yang_argument_get(yp); + if (strcmp(pi2, pi) == 0){ + clicon_err(OE_YANG, EFAULT, "Prefix %s in module %s is not unique but should be (see RFC 7950 7.1.4)", + pi, yang_argument_get(ym)); + goto done; + } + } + } + retval = 0; + done: + return retval; +} + /*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree. * * @param[in] ys Yang statement @@ -1913,24 +1967,29 @@ ys_populate(yang_stmt *ys, clicon_handle h = (clicon_handle)arg; switch(ys->ys_keyword){ - case Y_LIST: - if (ys_populate_list(h, ys) < 0) - goto done; - break; - case Y_RANGE: - if (ys_populate_range(h, ys) < 0) + case Y_IDENTITY: + if (ys_populate_identity(h, ys, NULL) < 0) goto done; break; case Y_LENGTH: if (ys_populate_length(h, ys) < 0) goto done; break; - case Y_TYPE: - if (ys_populate_type(h, ys) < 0) + case Y_LIST: + if (ys_populate_list(h, ys) < 0) goto done; break; - case Y_IDENTITY: - if (ys_populate_identity(h, ys, NULL) < 0) + case Y_MODULE: + case Y_SUBMODULE: + if (ys_populate_module_submodule(h, ys) < 0) + goto done; + break; + case Y_RANGE: + if (ys_populate_range(h, ys) < 0) + goto done; + break; + case Y_TYPE: + if (ys_populate_type(h, ys) < 0) goto done; break; case Y_UNIQUE: @@ -1949,7 +2008,6 @@ ys_populate(yang_stmt *ys, return retval; } - /*! Run after grouping expand and augment * @see ys_populate run before grouping expand and augment */ diff --git a/lib/src/clixon_yang_parse_lib.c b/lib/src/clixon_yang_parse_lib.c index 9002cb5b..f7b7fb36 100644 --- a/lib/src/clixon_yang_parse_lib.c +++ b/lib/src/clixon_yang_parse_lib.c @@ -978,7 +978,7 @@ ys_list_check(clicon_handle h, /*! Parse top yang module including all its sub-modules. Expand and populate yang tree * * Perform secondary actions after yang parsing. These actions cannot be made at - * compile-time for various reasons. + * parse-time for various reasons. * These includes: * - Detect imported yang specs that are not loaded and load and parse them too * - Check cardinality of yang (that nr of children match) @@ -1015,14 +1015,17 @@ yang_parse_post(clicon_handle h, goto done; /* 3: Check features: check if enabled and remove disabled features */ - for (i=modnr; iys_stmt[i]) < 0) goto done; /* 4: Go through parse tree and populate it with cv types */ - for (i=modnr; iys_stmt[i], h) < 0) /* Alt: make a yang_apply0 */ + goto done; if (yang_apply(yspec->ys_stmt[i], -1, ys_populate, (void*)h) < 0) goto done; + } /* 5: Resolve all types: populate type caches. Requires eg length/range cvecs * from ys_populate step. diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 7eb07f77..c9d3cc95 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -526,9 +526,9 @@ outoflength(uint64_t u64, * @param[in] regexps Vector of compiled regexps * @param[out] reason If given, and return value is 0, contains malloced str - * @retval -1 Error (fatal), with errno set to indicate error - * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() - * @retval 1 Validation OK + * @retval -1 Error (fatal), with errno set to indicate error + * @retval 0 Validation not OK, malloced reason is returned. Free reason with free() + * @retval 1 Validation OK * @note reason if given must be freed by caller * @see cv_validate Corresponding type check in cligen */ @@ -661,7 +661,7 @@ cv_validate1(clicon_handle h, found = 0; yi = NULL; if (str != NULL) { - str = clixon_trim2(str, " \t\n"); /* May be misplaced, strip earlier? */ + // str = clixon_trim2(str, " \t\n"); /* May be misplaced, strip earlier? */ while ((yi = yn_each(yrestype, yi)) != NULL){ if (yang_keyword_get(yi) != Y_ENUM) continue; diff --git a/test/test_nacm.sh b/test/test_nacm.sh index be8fb4c7..fb90a352 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -39,7 +39,7 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } diff --git a/test/test_nacm_credentials.sh b/test/test_nacm_credentials.sh index f50443f5..36597d7b 100755 --- a/test/test_nacm_credentials.sh +++ b/test/test_nacm_credentials.sh @@ -25,7 +25,7 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh index 9b573a11..e874bb88 100755 --- a/test/test_nacm_default.sh +++ b/test/test_nacm_default.sh @@ -39,7 +39,7 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 2bc1784b..3ebb2d17 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -42,7 +42,7 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } @@ -112,7 +112,7 @@ RULES=$(cat <key43val43 EOF ) -# NOTE use of translate^ has nothing to do with the CLI translate semantics +# NOTE use of translate^ has nothing to do with the CLI translate semantics CONFUSING! new "test params: -f $cfg -- -s" @@ -147,30 +147,26 @@ new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" new "enable nacm" -expecteq "$(curl -u andy:bar -sS -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 "" +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 #----READ access #user:admin new "admin read ok" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":{"translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}} - ' +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 'HTTP/1.1 200 OK' '{"clixon-example:translate":{"translate":\[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}\]}}' new "admin read netconf ok" expecteof "$clixon_netconf -U andy -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' new "admin read element ok" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 '{"clixon-example:value":"val42"} - ' +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 'HTTP/1.1 200 OK' '{"clixon-example:value":"val42"}' new "admin read other module OK" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"nacm-example:x":42} - ' +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 200 OK' '{"nacm-example:x":42}' new "admin read state OK" -expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} - ' +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "admin read top ok (all)" ret=$(curl -u andy:bar -sS -X GET http://localhost/restconf/data) @@ -183,65 +179,59 @@ fi #user:limit new "limit read ok" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"clixon-example:translate":{"translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]}} - ' +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 'HTTP/1.1 200 OK' '{"clixon-example:translate":{"translate":\[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}\]}}' new "limit read netconf ok" expecteof "$clixon_netconf -U wilma -qf $cfg" 0 ']]>]]>' '^key42val42key43val43]]>]]>$' new "limit read element ok" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 '{"clixon-example:value":"val42"} - ' +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 'HTTP/1.1 200 OK' '{"clixon-example:value":"val42"}' new "limit read other module fail" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}} ' +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' new "limit read state OK" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"clixon-example:state":{"op":["41","42","43"]}} - ' +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 200 OK' '{"clixon-example:state":{"op":\["41","42","43"\]}}' new "limit read top ok (part)" -expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data)" 0 '{"data":{"clixon-example:translate":{"translate":[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}]},"clixon-example:state":{"op":["41","42","43"]}}} - ' +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data)" 0 'HTTP/1.1 200 OK' '{"data":{"clixon-example:translate":{"translate":\[{"k":"key42","value":"val42"},{"k":"key43","value":"val43"}\]},"clixon-example:state":{"op":\["41","42","43"\]}}}' #user:guest -new "guest read fail" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +new "guest read forbidden" +expectpart "$(curl -u guest:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read netconf fail" expecteof "$clixon_netconf -U guest -qf $cfg" 0 ']]>]]>' '^applicationaccess-deniederrordefault deny]]>]]>$' -new "guest read element fail" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +new "guest read element forbidden" +expectpart "$(curl -u guest:bar -siS -X GET http://localhost/restconf/data/clixon-example:translate/translate=key42/value)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read other module fail" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +expectpart "$(curl -u guest:bar -siS -X GET http://localhost/restconf/data/nacm-example:x)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read state fail" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +expectpart "$(curl -u guest:bar -siS -X GET http://localhost/restconf/data/clixon-example:state)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "guest read top ok (part)" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}} ' +expectpart "$(curl -u guest:bar -siS -X GET http://localhost/restconf/data)" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' #------- RPC operation new "admin rpc ok" -expecteq "$(curl -u andy:bar -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"clixon-example:output":{"x":"78","y":"42"}} - ' +expectpart "$(curl -u andy:bar -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"78"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 200 OK' '{"clixon-example:output":{"x":"78","y":"42"}}' new "admin rpc netconf ok" expecteof "$clixon_netconf -U andy -qf $cfg" 0 '0]]>]]>' 0 '^042]]>]]>$' new "limit rpc ok" -expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 '{"clixon-example:output":{"x":"42","y":"42"}} - ' +expectpart "$(curl -u wilma:bar -si -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 'HTTP/1.1 200 OK' '{"clixon-example:output":{"x":"42","y":"42"}}' new "limit rpc netconf ok" expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '0]]>]]>' 0 '^042]]>]]>$' new "guest rpc fail" -expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}} ' +expectpart "$(curl -u guest:bar -si -X POST http://localhost/restconf/operations/clixon-example:example -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":42}}' )" 0 'HTTP/1.1 403 Forbidden' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}' new "guest rpc netconf fail" expecteof "$clixon_netconf -U guest -qf $cfg" 0 '0]]>]]>' 0 '^applicationaccess-deniederroraccess denied]]>]]>$' @@ -249,19 +239,16 @@ expecteof "$clixon_netconf -U guest -qf $cfg" 0 ' $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } @@ -104,7 +104,7 @@ RULES=$(cat < deny-update nacm-example - read update + update deny @@ -121,7 +121,7 @@ RULES=$(cat < deny-create-delete nacm-example - read create delete + create delete deny diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 5c20acb9..f1648462 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -59,7 +59,7 @@ cat < $fyang module nacm-example{ yang-version 1.1; namespace "urn:example:nacm"; - prefix nacm; + prefix nex; import clixon-example { prefix ex; } diff --git a/test/test_restconf_jukebox.sh b/test/test_restconf_jukebox.sh index cacbd222..773fdff8 100755 --- a/test/test_restconf_jukebox.sh +++ b/test/test_restconf_jukebox.sh @@ -242,9 +242,7 @@ new 'B.3.5. "point" Parameter leaf-list 4 before 3' expectpart "$(curl -si -X POST -H 'Content-Type: application/yang-data+json' -d '{"example-jukebox:extra":"4"}' http://localhost/restconf/data?insert=before\&point=%2Fexample-jukebox%3Aextra%3D3 )" 0 "HTTP/1.1 201 Created" 'Location: http://localhost/restconf/data/example-jukebox:extra=4' new 'B.3.5. "insert/point" leaf-list check order (2,4,3,1)' -expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431' - -if false; then # NYI +expectpart "$(curl -si -X GET http://localhost/restconf/data/example-jukebox:extra -H 'Accept: application/yang-data+xml')" 0 'HTTP/1.1 200 OK' '2431' new "B.2.2. Detect Datastore Resource Entity-Tag Change" # XXX done except entity-changed new 'B.3.3. "fields" Parameter' @@ -253,8 +251,6 @@ new 'B.3.7. "start-time" Parameter' new 'B.3.8. "stop-time" Parameter' new 'B.3.9. "with-defaults" Parameter' -fi # NYI - if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf