* NACM extension (RFC8341)
* NACM module support (RFC8341 A1+A2)
* Recovery user "_nacm_recovery" added.
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
* Example user changed adm1 to andy to comply with RFC8341 example
* Yang code upgrade (RFC7950)
* RPC method input parameters validated
* see https://github.com/clicon/clixon/issues/4
* Correct XML namespace handling
* XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration:
```
<rpc><my-own-method></rpc> # Wrong but accepted
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # Correct
<my-own-method xmlns="http://example.net/me/my-own/1.0">
</rpc>
```
* To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default)
* XML to JSON translator support for mapping xmlns attribute to module name prefix.
* Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0"
* See https://github.com/clicon/clixon/issues/49
* Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3)
* debug rpc added in example application (should be in clixon-config).
This commit is contained in:
parent
e5c0b06cf9
commit
ae1af8da9e
63 changed files with 1852 additions and 3492 deletions
|
|
@ -80,5 +80,5 @@ distclean: clean
|
|||
do (cd $$i; $(MAKE) $(MFLAGS) distclean); done; \
|
||||
(cd clixon; $(MAKE) $(MFLAGS) $@)
|
||||
|
||||
tags:
|
||||
TAGS:
|
||||
find $(srcdir) -name '*.[chyl]' -print | etags -
|
||||
|
|
|
|||
|
|
@ -36,9 +36,19 @@
|
|||
#ifndef _CLIXON_NACM_H
|
||||
#define _CLIXON_NACM_H
|
||||
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
/* RFC8341 defines a "recovery session" as outside the scope.
|
||||
* Clixon defines this user as having special admin rights to expemt from
|
||||
* all access control enforcements
|
||||
*/
|
||||
#define NACM_RECOVERY_USER "_nacm_recovery"
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int nacm_access(clicon_handle h, char *mode, char *name, char *username, cbuf *cbret);
|
||||
int nacm_access(clicon_handle h, char *rpc, char *module,
|
||||
char *username, cbuf *cbret);
|
||||
|
||||
#endif /* _CLIXON_NACM_H */
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@
|
|||
/*
|
||||
* Constants
|
||||
*/
|
||||
/* If rpc call does not have a namespace (eg w xmlns) then use the default NETCONF
|
||||
* namespace (rfc6241 3.1)
|
||||
*/
|
||||
#define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0"
|
||||
|
||||
/*
|
||||
* Types
|
||||
|
|
@ -81,10 +85,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg);
|
|||
#define XML_FLAG_NONE 0x10 /* Node is added as NONE */
|
||||
|
||||
|
||||
/* Sort and binary search of XML children
|
||||
* Experimental
|
||||
/* Iterate through modules to find the matching datanode
|
||||
* or rpc if no xmlns attribute specifies namespace.
|
||||
* This is loose semantics of finding namespaces.
|
||||
* And it is wrong, but is the way Clixon originally was written."
|
||||
* @see CLICON_XML_NS_ITERATE clixon configure option
|
||||
*/
|
||||
extern int xml_child_sort;
|
||||
extern int _CLICON_XML_NS_ITERATE;
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
|
|
@ -94,6 +101,7 @@ char *xml_name(cxobj *xn);
|
|||
int xml_name_set(cxobj *xn, char *name);
|
||||
char *xml_namespace(cxobj *xn);
|
||||
int xml_namespace_set(cxobj *xn, char *name);
|
||||
int xml2ns(cxobj *x, char *localname, char **namespace);
|
||||
cxobj *xml_parent(cxobj *xn);
|
||||
int xml_parent_set(cxobj *xn, cxobj *parent);
|
||||
|
||||
|
|
@ -129,6 +137,8 @@ int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
|
|||
|
||||
char *xml_body(cxobj *xn);
|
||||
cxobj *xml_body_get(cxobj *xn);
|
||||
char *xml_find_type_value(cxobj *xn_parent, char *prefix,
|
||||
char *name, enum cxobj_type type);
|
||||
char *xml_find_value(cxobj *xn_parent, char *name);
|
||||
char *xml_find_body(cxobj *xn, char *name);
|
||||
cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
*/
|
||||
int xml2txt(FILE *f, cxobj *x, int level);
|
||||
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
|
||||
int xml_yang_validate_rpc(cxobj *xrpc);
|
||||
int xml_yang_validate_add(cxobj *xt, void *arg);
|
||||
int xml_yang_validate_all(cxobj *xt, void *arg);
|
||||
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
|
||||
|
|
@ -60,6 +61,7 @@ int xml_default(cxobj *x, void *arg);
|
|||
int xml_order(cxobj *x, void *arg);
|
||||
int xml_sanity(cxobj *x, void *arg);
|
||||
int xml_non_config_data(cxobj *xt, void *arg);
|
||||
int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec);
|
||||
int xml_spec_populate(cxobj *x, void *arg);
|
||||
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
|
||||
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
/*
|
||||
* Types
|
||||
*/
|
||||
struct xml;
|
||||
/*! YANG keywords from RFC6020.
|
||||
* See also keywords generated by yacc/bison in clicon_yang_parse.tab.h, but they start with K_
|
||||
* instead of Y_
|
||||
|
|
@ -159,7 +160,6 @@ typedef enum yang_class yang_class;
|
|||
*/
|
||||
#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES)
|
||||
|
||||
|
||||
/* Yang schema node .
|
||||
* See RFC 7950 Sec 3:
|
||||
* o schema node: A node in the schema tree. One of action, container,
|
||||
|
|
@ -253,15 +253,18 @@ char *yang_key2str(int keyword);
|
|||
char *yarg_prefix(yang_stmt *ys);
|
||||
char *yarg_id(yang_stmt *ys);
|
||||
int yang_nodeid_split(char *nodeid, char **prefix, char **id);
|
||||
int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp);
|
||||
yang_stmt *ys_module(yang_stmt *ys);
|
||||
yang_spec *ys_spec(yang_stmt *ys);
|
||||
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
|
||||
yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace);
|
||||
yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument);
|
||||
int yang_match(yang_node *yn, int keyword, char *argument);
|
||||
yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
|
||||
yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
|
||||
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class);
|
||||
char *yang_find_myprefix(yang_stmt *ys);
|
||||
char *yang_find_mynamespace(yang_stmt *ys);
|
||||
int yang_order(yang_stmt *y);
|
||||
int yang_print(FILE *f, yang_node *yn);
|
||||
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
|
||||
|
|
|
|||
|
|
@ -90,22 +90,43 @@ enum childtype{
|
|||
ANY_CHILD, /* eg <a><b/></a> or <a><b/><c/></a> */
|
||||
};
|
||||
|
||||
/*! Number of children EXCEPT attributes
|
||||
* @param[in] xn xml node
|
||||
* @retval number of children in XML tree (except children of type CX_ATTR)
|
||||
* @see xml_child_nr
|
||||
*/
|
||||
static int
|
||||
xml_child_nr_noattr(cxobj *xn)
|
||||
{
|
||||
cxobj *x = NULL;
|
||||
int nr = 0;
|
||||
|
||||
while ((x = xml_child_each(xn, x, -1)) != NULL) {
|
||||
if (xml_type(x) != CX_ATTR)
|
||||
nr++;
|
||||
}
|
||||
return nr;
|
||||
}
|
||||
|
||||
/*! x is element and has exactly one child which in turn has none
|
||||
* remove attributes from x
|
||||
* Clone from clixon_xml_map.c
|
||||
*/
|
||||
static enum childtype
|
||||
childtype(cxobj *x)
|
||||
{
|
||||
cxobj *xc1; /* the only child of x */
|
||||
int clen; /* nr of children */
|
||||
|
||||
clen = xml_child_nr_noattr(x);
|
||||
if (xml_type(x) != CX_ELMNT)
|
||||
return -1; /* n/a */
|
||||
if (xml_child_nr(x) == 0)
|
||||
if (clen == 0)
|
||||
return NULL_CHILD;
|
||||
if (xml_child_nr(x) > 1)
|
||||
if (clen > 1)
|
||||
return ANY_CHILD;
|
||||
xc1 = xml_child_i(x, 0); /* From here exactly one child */
|
||||
if (xml_child_nr(xc1) == 0 && xml_type(xc1)==CX_BODY)
|
||||
if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY)
|
||||
return BODY_CHILD;
|
||||
else
|
||||
return ANY_CHILD;
|
||||
|
|
@ -267,13 +288,13 @@ json_str_escape(char *str)
|
|||
+----------+--------------+--------------+--------------+
|
||||
*/
|
||||
static int
|
||||
xml2json1_cbuf(cbuf *cb,
|
||||
cxobj *x,
|
||||
xml2json1_cbuf(cbuf *cb,
|
||||
cxobj *x,
|
||||
enum array_element_type arraytype,
|
||||
int level,
|
||||
int pretty,
|
||||
int flat,
|
||||
int bodystr)
|
||||
int level,
|
||||
int pretty,
|
||||
int flat,
|
||||
int bodystr)
|
||||
{
|
||||
int retval = -1;
|
||||
int i;
|
||||
|
|
@ -281,10 +302,30 @@ xml2json1_cbuf(cbuf *cb,
|
|||
enum childtype childt;
|
||||
enum array_element_type xc_arraytype;
|
||||
yang_stmt *ys;
|
||||
yang_stmt *ymod; /* yang module */
|
||||
yang_spec *yspec = NULL; /* yang spec */
|
||||
int bodystr0=1;
|
||||
char *str;
|
||||
char *prefix=NULL; /* prefix / local namespace name */
|
||||
char *namespace=NULL; /* namespace uri */
|
||||
char *modname=NULL; /* Module name */
|
||||
|
||||
/* If x is labelled with a default namespace, it should be translated
|
||||
* to a module name.
|
||||
* Harder if x has a prefix, then that should also be translated to associated
|
||||
* module name
|
||||
*/
|
||||
prefix = xml_namespace(x);
|
||||
if (xml2ns(x, prefix, &namespace) < 0)
|
||||
goto done;
|
||||
if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */
|
||||
yspec = ys_spec(ys);
|
||||
/* Find module name associated with namspace URI */
|
||||
if (namespace && yspec &&
|
||||
(ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){
|
||||
modname = ymod->ys_argument;
|
||||
}
|
||||
childt = childtype(x);
|
||||
ys = xml_spec(x);
|
||||
if (pretty==2)
|
||||
cprintf(cb, "#%s_array, %s_child ",
|
||||
arraytype2str(arraytype),
|
||||
|
|
@ -292,7 +333,6 @@ xml2json1_cbuf(cbuf *cb,
|
|||
switch(arraytype){
|
||||
case BODY_ARRAY:{
|
||||
if (bodystr){
|
||||
char *str;
|
||||
if ((str = json_str_escape(xml_value(x))) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "\"%s\"", str);
|
||||
|
|
@ -300,14 +340,13 @@ xml2json1_cbuf(cbuf *cb,
|
|||
}
|
||||
else
|
||||
cprintf(cb, "%s", xml_value(x));
|
||||
|
||||
break;
|
||||
}
|
||||
case NO_ARRAY:
|
||||
if (!flat){
|
||||
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
|
||||
if (xml_namespace(x))
|
||||
cprintf(cb, "%s:", xml_namespace(x));
|
||||
if (modname) /* XXX should remove this? */
|
||||
cprintf(cb, "%s:", modname);
|
||||
cprintf(cb, "%s\": ", xml_name(x));
|
||||
}
|
||||
switch (childt){
|
||||
|
|
@ -326,8 +365,8 @@ xml2json1_cbuf(cbuf *cb,
|
|||
case FIRST_ARRAY:
|
||||
case SINGLE_ARRAY:
|
||||
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
|
||||
if (xml_namespace(x))
|
||||
cprintf(cb, "%s:", xml_namespace(x));
|
||||
if (modname)
|
||||
cprintf(cb, "%s:", modname);
|
||||
cprintf(cb, "%s\": ", xml_name(x));
|
||||
level++;
|
||||
cprintf(cb, "[%s%*s",
|
||||
|
|
@ -368,7 +407,7 @@ xml2json1_cbuf(cbuf *cb,
|
|||
break;
|
||||
}
|
||||
/* Check for typed sub-body if:
|
||||
* arracytype=* but chilt-type is BODY_CHILD
|
||||
* arraytype=* but child-type is BODY_CHILD
|
||||
* This is code for writing <a>42</a> as "a":42 and not "a":"42"
|
||||
*/
|
||||
if (childt == BODY_CHILD && ys!=NULL &&
|
||||
|
|
@ -393,6 +432,8 @@ xml2json1_cbuf(cbuf *cb,
|
|||
|
||||
for (i=0; i<xml_child_nr(x); i++){
|
||||
xc = xml_child_i(x, i);
|
||||
if (xml_type(xc) == CX_ATTR)
|
||||
continue; /* XXX Only xmlns attributes mapped */
|
||||
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
|
||||
xc,
|
||||
xml_child_i(x, i+1));
|
||||
|
|
@ -401,7 +442,7 @@ xml2json1_cbuf(cbuf *cb,
|
|||
xc_arraytype,
|
||||
level+1, pretty, 0, bodystr0) < 0)
|
||||
goto done;
|
||||
if (i<xml_child_nr(x)-1)
|
||||
if (i<xml_child_nr_noattr(x)-1)
|
||||
cprintf(cb, ",%s", pretty?"\n":"");
|
||||
}
|
||||
switch (arraytype){
|
||||
|
|
|
|||
|
|
@ -128,27 +128,28 @@ nacm_match_access(char *access_operations,
|
|||
static int
|
||||
nacm_match_rule(clicon_handle h,
|
||||
char *name,
|
||||
char *module,
|
||||
cxobj *xrule,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
// cxobj *x;
|
||||
char *module_name;
|
||||
char *rpc_name;
|
||||
char *module_rule; /* rule module name */
|
||||
char *rpc_rule;
|
||||
char *access_operations;
|
||||
char *action;
|
||||
|
||||
module_name = xml_find_body(xrule, "module-name");
|
||||
rpc_name = xml_find_body(xrule, "rpc-name");
|
||||
module_rule = xml_find_body(xrule, "module-name");
|
||||
rpc_rule = xml_find_body(xrule, "rpc-name");
|
||||
/* XXX access_operations can be a set of bits */
|
||||
access_operations = xml_find_body(xrule, "access-operations");
|
||||
action = xml_find_body(xrule, "action");
|
||||
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
|
||||
module_name, rpc_name, access_operations, action);
|
||||
if (module_name && strcmp(module_name,"*")==0){
|
||||
module_rule, rpc_rule, access_operations, action);
|
||||
if (module_rule &&
|
||||
(strcmp(module_rule,"*")==0 || strcmp(module_rule,module)==0)){
|
||||
if (nacm_match_access(access_operations, "exec")){
|
||||
if (rpc_name==NULL ||
|
||||
strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){
|
||||
if (rpc_rule==NULL ||
|
||||
strcmp(rpc_rule, "*")==0 || strcmp(rpc_rule, name)==0){
|
||||
/* Here is a matching rule */
|
||||
if (action && strcmp(action, "permit")==0){
|
||||
retval = 1;
|
||||
|
|
@ -166,61 +167,43 @@ nacm_match_rule(clicon_handle h,
|
|||
retval = 2; /* no matching rule */
|
||||
done:
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! Make nacm access control
|
||||
/*! Process a nacm message control
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] mode NACMmode, internal or external
|
||||
* @param[in] name rpc name
|
||||
* @param[in] username
|
||||
* @param[in] xtop
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Not access and cbret set
|
||||
* @retval 1 Access
|
||||
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
||||
*/
|
||||
int
|
||||
nacm_access(clicon_handle h,
|
||||
char *mode,
|
||||
char *name,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
static int
|
||||
nacm_rpc_validation(clicon_handle h,
|
||||
char *name,
|
||||
char *module,
|
||||
char *username,
|
||||
cxobj *xtop,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xacm;
|
||||
cxobj *x;
|
||||
cxobj *xrlist;
|
||||
cxobj *xrule;
|
||||
char *enabled = NULL;
|
||||
cxobj **gvec = NULL; /* groups */
|
||||
size_t glen;
|
||||
cxobj *xrlist;
|
||||
cxobj **rlistvec = NULL; /* rule-list */
|
||||
size_t rlistlen;
|
||||
cxobj **rvec = NULL; /* rules */
|
||||
size_t rlen;
|
||||
int ret;
|
||||
int i, j;
|
||||
char *exec_default = NULL;
|
||||
int ret;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
|
||||
otherwise get it from internal configuration */
|
||||
if (strcmp(mode, "external")==0){
|
||||
if ((xtop = clicon_nacm_ext(h)) == NULL){
|
||||
clicon_err(OE_XML, 0, "No nacm external tree");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else if (strcmp(mode, "internal")==0){
|
||||
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
||||
operation is permitted. (or config does not exist) */
|
||||
|
|
@ -235,6 +218,8 @@ nacm_access(clicon_handle h,
|
|||
|
||||
/* 2. If the requesting session is identified as a recovery session,
|
||||
then the protocol operation is permitted. NYI */
|
||||
if (strcmp(username, NACM_RECOVERY_USER) == 0)
|
||||
goto permit;
|
||||
|
||||
/* 3. If the requested operation is the NETCONF <close-session>
|
||||
protocol operation, then the protocol operation is permitted.
|
||||
|
|
@ -280,7 +265,7 @@ nacm_access(clicon_handle h,
|
|||
for (j=0; j<rlen; j++){
|
||||
xrule = rvec[j];
|
||||
/* -1 error, 0 deny, 1 permit, 2 continue */
|
||||
if ((ret = nacm_match_rule(h, name, xrule, cbret)) < 0)
|
||||
if ((ret = nacm_match_rule(h, name, module, xrule, cbret)) < 0)
|
||||
goto done;
|
||||
switch(ret){
|
||||
case 0: /* deny */
|
||||
|
|
@ -318,8 +303,6 @@ nacm_access(clicon_handle h,
|
|||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
||||
if (strcmp(mode, "internal")==0 && xtop)
|
||||
xml_free(xtop);
|
||||
if (gvec)
|
||||
free(gvec);
|
||||
if (rlistvec)
|
||||
|
|
@ -332,3 +315,57 @@ nacm_access(clicon_handle h,
|
|||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Make nacm access control
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name rpc name
|
||||
* @param[in] username
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Not access and cbret set
|
||||
* @retval 1 Access
|
||||
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
||||
*/
|
||||
int
|
||||
nacm_access(clicon_handle h,
|
||||
char *rpc,
|
||||
char *module,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xtop = NULL;
|
||||
char *mode;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (mode == NULL || strcmp(mode, "disabled") == 0){
|
||||
retval = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
|
||||
otherwise get it from internal configuration */
|
||||
if (strcmp(mode, "external")==0){
|
||||
if ((xtop = clicon_nacm_ext(h)) == NULL){
|
||||
clicon_err(OE_XML, 0, "No nacm external tree");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else if (strcmp(mode, "internal")==0){
|
||||
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
|
||||
goto done;
|
||||
}
|
||||
/* Do the real nacm processing */
|
||||
if ((retval = nacm_rpc_validation(h, rpc, module, username, xtop, cbret)) < 0)
|
||||
goto done;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
||||
if (strcmp(mode, "internal")==0 && xtop)
|
||||
xml_free(xtop);
|
||||
return retval;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -983,19 +983,14 @@ netconf_module_load(clicon_handle h)
|
|||
{
|
||||
int retval = -1;
|
||||
cxobj *xc;
|
||||
// cxobj *x;
|
||||
yang_spec *yspec;
|
||||
|
||||
yspec = clicon_dbspec_yang(h);
|
||||
/* Load yang spec */
|
||||
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
|
||||
goto done;
|
||||
if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
|
||||
goto done;
|
||||
if ((xc = clicon_conf_xml(h)) == NULL){
|
||||
clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Enable features (hardcoded here) */
|
||||
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:candidate</CLICON_FEATURE>", yspec, &xc) < 0)
|
||||
goto done;
|
||||
|
|
@ -1005,6 +1000,12 @@ netconf_module_load(clicon_handle h)
|
|||
goto done;
|
||||
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>", yspec, &xc) < 0)
|
||||
goto done;
|
||||
|
||||
/* Load yang spec */
|
||||
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
|
||||
goto done;
|
||||
if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
#include "clixon_log.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_xml_sort.h"
|
||||
#include "clixon_options.h"
|
||||
#include "clixon_plugin.h"
|
||||
#include "clixon_xpath_ctx.h"
|
||||
|
|
@ -242,6 +243,9 @@ clicon_options_main(clicon_handle h,
|
|||
clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix);
|
||||
goto done;
|
||||
}
|
||||
#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */
|
||||
_CLICON_XML_NS_ITERATE = 1;
|
||||
#endif
|
||||
/* Read configfile first without yangspec, for bootstrapping */
|
||||
if (parse_configfile(h, configfile, yspec, &xconfig) < 0)
|
||||
goto done;
|
||||
|
|
@ -267,6 +271,9 @@ clicon_options_main(clicon_handle h,
|
|||
xml_child_sort = 1;
|
||||
else
|
||||
xml_child_sort = 0;
|
||||
#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */
|
||||
_CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE");
|
||||
#endif
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -826,7 +826,8 @@ clicon_rpc_debug(clicon_handle h,
|
|||
char *username;
|
||||
|
||||
username = clicon_username_get(h);
|
||||
if ((msg = clicon_msg_encode("<rpc username=\"%s\"><debug><level>%d</level></debug></rpc>", username?username:"", level)) == NULL)
|
||||
/* XXX: hardcoded example yang, should be clixon-config!!! */
|
||||
if ((msg = clicon_msg_encode("<rpc username=\"%s\"><debug xmlns=\"urn:example:clixon\"><level>%d</level></debug></rpc>", username?username:"", level)) == NULL)
|
||||
goto done;
|
||||
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -108,17 +108,13 @@
|
|||
* - Namespace name: For a name N in a namespace identified by a URI I, the
|
||||
* "namespace name" is I.
|
||||
* For a name N that is not in a namespace, the "namespace name" has no value.
|
||||
* - Local name: In either case the "local name" is N.
|
||||
* - Local name: In either case the "local name" is N (also "prefix")
|
||||
* It is this combination of the universally managed URI namespace with the
|
||||
* vocabulary's local names that is effective in avoiding name clashes.
|
||||
*/
|
||||
struct xml{
|
||||
char *x_name; /* name of node */
|
||||
char *x_namespace; /* namespace, if any */
|
||||
#ifdef notyet
|
||||
char *x_namespacename; /* namespace name (or NULL) */
|
||||
char *x_localname; /* Local name N as defined above */
|
||||
#endif
|
||||
char *x_prefix; /* namespace localname N, called prefix */
|
||||
struct xml *x_up; /* parent node in hierarchy if any */
|
||||
struct xml **x_childvec; /* vector of children nodes */
|
||||
int x_childvec_len;/* length of vector */
|
||||
|
|
@ -130,6 +126,17 @@ struct xml{
|
|||
reference, dont free */
|
||||
};
|
||||
|
||||
/*
|
||||
* Variables
|
||||
*/
|
||||
/* Iterate through modules to find the matching datanode
|
||||
* or rpc if no xmlns attribute specifies namespace.
|
||||
* This is loose semantics of finding namespaces.
|
||||
* And it is wrong, but is the way Clixon originally was written."
|
||||
* @see CLICON_XML_NS_ITERATE clixon configure option
|
||||
*/
|
||||
int _CLICON_XML_NS_ITERATE = 0;
|
||||
|
||||
/* Mapping between xml type <--> string */
|
||||
static const map_str2int xsmap[] = {
|
||||
{"error", CX_ERROR},
|
||||
|
|
@ -189,29 +196,31 @@ xml_name_set(cxobj *xn,
|
|||
/*! Get namespace of xnode
|
||||
* @param[in] xn xml node
|
||||
* @retval namespace of xml node
|
||||
* XXX change to xml_localname
|
||||
*/
|
||||
char*
|
||||
xml_namespace(cxobj *xn)
|
||||
{
|
||||
return xn->x_namespace;
|
||||
return xn->x_prefix;
|
||||
}
|
||||
|
||||
/*! Set name space of xnode, namespace is copied
|
||||
* @param[in] xn xml node
|
||||
* @param[in] namespace new namespace, null-terminated string, copied by function
|
||||
* @param[in] localname new namespace, null-terminated string, copied by function
|
||||
* @retval -1 on error with clicon-err set
|
||||
* @retval 0 OK
|
||||
* XXX change to xml_localname_set
|
||||
*/
|
||||
int
|
||||
xml_namespace_set(cxobj *xn,
|
||||
char *namespace)
|
||||
char *localname)
|
||||
{
|
||||
if (xn->x_namespace){
|
||||
free(xn->x_namespace);
|
||||
xn->x_namespace = NULL;
|
||||
if (xn->x_prefix){
|
||||
free(xn->x_prefix);
|
||||
xn->x_prefix = NULL;
|
||||
}
|
||||
if (namespace){
|
||||
if ((xn->x_namespace = strdup(namespace)) == NULL){
|
||||
if (localname){
|
||||
if ((xn->x_prefix = strdup(localname)) == NULL){
|
||||
clicon_err(OE_XML, errno, "strdup");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -219,12 +228,57 @@ xml_namespace_set(cxobj *xn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! See if xmlns:<namespace>=<uri> exists, if so return <uri>
|
||||
|
||||
/*! Given an xml tree return URI namespace: default or localname given
|
||||
*
|
||||
* Given an XML tree and a prefix (or NULL) return URI namespace.
|
||||
* @param[in] x XML tree
|
||||
* @param[in] prefix prefix/ns localname. If NULL then return default.
|
||||
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xmlns_check XXX coordinate
|
||||
*/
|
||||
int
|
||||
xml2ns(cxobj *x,
|
||||
char *prefix,
|
||||
char **namespace)
|
||||
{
|
||||
int retval = -1;
|
||||
char *ns;
|
||||
cxobj *xp;
|
||||
|
||||
if (prefix != NULL) /* xmlns:<prefix> */
|
||||
ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
|
||||
else /* default ns */
|
||||
ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
|
||||
|
||||
/* namespace not found, try parent */
|
||||
if (ns == NULL){
|
||||
if ((xp = xml_parent(x)) != NULL){
|
||||
if (xml2ns(xp, prefix, &ns) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* If no parent, return default namespace if defined */
|
||||
#if defined(DEFAULT_XML_RPC_NAMESPACE)
|
||||
else
|
||||
ns = DEFAULT_XML_RPC_NAMESPACE;
|
||||
#endif
|
||||
}
|
||||
if (namespace)
|
||||
*namespace = ns;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! See if xmlns:[<localname>=]<uri> exists, if so return <uri>
|
||||
*
|
||||
* @param[in] xn XML node
|
||||
* @param[in] nsn Namespace name
|
||||
* @retval URI return associated URI if found
|
||||
* @retval NULL No namespace name binding found for nsn
|
||||
* @see xml2ns XXX coordinate
|
||||
*/
|
||||
static char *
|
||||
xmlns_check(cxobj *xn,
|
||||
|
|
@ -233,7 +287,7 @@ xmlns_check(cxobj *xn,
|
|||
cxobj *x = NULL;
|
||||
char *xns;
|
||||
|
||||
while ((x = xml_child_each(xn, x, -1)) != NULL)
|
||||
while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL)
|
||||
if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 &&
|
||||
strcmp(xml_name(x), nsn) == 0)
|
||||
return xml_value(x);
|
||||
|
|
@ -248,7 +302,7 @@ xmlns_check(cxobj *xn,
|
|||
* @note This function is grossly inefficient
|
||||
*/
|
||||
static int
|
||||
xml_namespace_check(cxobj *xn,
|
||||
xml_localname_check(cxobj *xn,
|
||||
void *arg)
|
||||
{
|
||||
cxobj *xp = NULL;
|
||||
|
|
@ -886,6 +940,42 @@ xml_body_get(cxobj *xt)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*! Find and return the value of an xml child of specific type
|
||||
*
|
||||
* The value can be of an attribute only
|
||||
* @param[in] xt xml tree node
|
||||
* @param[in] prefix Prefix (namespace local name) or NULL
|
||||
* @param[in] name name of xml tree node (eg attr name or "body")
|
||||
* @retval val Pointer to the name string
|
||||
* @retval NULL No such node or no value in node
|
||||
* @code
|
||||
* char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR);
|
||||
* @endcode
|
||||
* @note, make a copy of the return value to use it properly
|
||||
* @see xml_find_value where a body can be found as well
|
||||
*/
|
||||
char *
|
||||
xml_find_type_value(cxobj *xt,
|
||||
char *prefix,
|
||||
char *name,
|
||||
enum cxobj_type type)
|
||||
{
|
||||
cxobj *x = NULL;
|
||||
int pmatch; /* prefix match */
|
||||
char *xprefix; /* xprefix */
|
||||
|
||||
while ((x = xml_child_each(xt, x, type)) != NULL) {
|
||||
xprefix = xml_namespace(x);
|
||||
if (prefix)
|
||||
pmatch = xprefix?strcmp(prefix,xprefix)==0:0;
|
||||
else
|
||||
pmatch = 1;
|
||||
if (pmatch && strcmp(name, xml_name(x)) == 0)
|
||||
return xml_value(x);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*! Find and return the value of a sub xml node
|
||||
*
|
||||
* The value can be of an attribute or body.
|
||||
|
|
@ -895,7 +985,7 @@ xml_body_get(cxobj *xt)
|
|||
* @retval NULL No such node or no value in node
|
||||
*
|
||||
* Note, make a copy of the return value to use it properly
|
||||
* See also xml_find_body
|
||||
* @see xml_find_body
|
||||
* Explaining picture:
|
||||
* xt --> x
|
||||
* x_name=name
|
||||
|
|
@ -983,8 +1073,8 @@ xml_free(cxobj *x)
|
|||
free(x->x_name);
|
||||
if (x->x_value)
|
||||
free(x->x_value);
|
||||
if (x->x_namespace)
|
||||
free(x->x_namespace);
|
||||
if (x->x_prefix)
|
||||
free(x->x_prefix);
|
||||
for (i=0; i<x->x_childvec_len; i++){
|
||||
if ((xc = x->x_childvec[i]) != NULL){
|
||||
xml_free(xc);
|
||||
|
|
@ -1028,6 +1118,8 @@ clicon_xml2file(FILE *f,
|
|||
char *val;
|
||||
char *encstr = NULL; /* xml encoded string */
|
||||
|
||||
if (x == NULL)
|
||||
goto ok;
|
||||
name = xml_name(x);
|
||||
namespace = xml_namespace(x);
|
||||
switch(xml_type(x)){
|
||||
|
|
@ -1097,6 +1189,7 @@ clicon_xml2file(FILE *f,
|
|||
default:
|
||||
break;
|
||||
}/* switch */
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (encstr)
|
||||
|
|
@ -1302,7 +1395,7 @@ _xml_parse(const char *str,
|
|||
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
|
||||
goto done;
|
||||
/* Verify namespaces after parsing */
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_namespace_check, NULL) < 0)
|
||||
if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0)
|
||||
goto done;
|
||||
/* Sort the complete tree after parsing */
|
||||
if (yspec){
|
||||
|
|
@ -1507,15 +1600,20 @@ int
|
|||
xml_copy_one(cxobj *x0,
|
||||
cxobj *x1)
|
||||
{
|
||||
char *s;
|
||||
|
||||
xml_type_set(x1, xml_type(x0));
|
||||
if (xml_value(x0)){ /* malloced string */
|
||||
if ((x1->x_value = strdup(x0->x_value)) == NULL){
|
||||
if ((s = xml_value(x0))){ /* malloced string */
|
||||
if ((x1->x_value = strdup(s)) == NULL){
|
||||
clicon_err(OE_XML, errno, "strdup");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (xml_name(x0)) /* malloced string */
|
||||
if ((xml_name_set(x1, xml_name(x0))) < 0)
|
||||
if ((s = xml_name(x0))) /* malloced string */
|
||||
if ((xml_name_set(x1, s)) < 0)
|
||||
return -1;
|
||||
if ((s = xml_namespace(x0))) /* malloced string */
|
||||
if ((xml_namespace_set(x1, s)) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1794,7 +1892,6 @@ xml_body_parse(cxobj *xb,
|
|||
if (retval < 0 && cv != NULL)
|
||||
cv_free(cv);
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! Parse an xml body as int32
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
/*! Load an xmldb storage plugin according to filename
|
||||
* If init function fails (not found, wrong version, etc) print a log and dont
|
||||
* add it.
|
||||
* @param[in] h CLicon handle
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] filename Actual filename including path
|
||||
*/
|
||||
int
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ validate_leafref(cxobj *xt,
|
|||
char *leafbody;
|
||||
|
||||
if ((leafrefbody = xml_body(xt)) == NULL)
|
||||
return 0;
|
||||
goto ok;
|
||||
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
|
||||
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
|
||||
goto done;
|
||||
|
|
@ -264,6 +264,7 @@ validate_leafref(cxobj *xt,
|
|||
leafrefbody);
|
||||
goto done;
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
if (xvec)
|
||||
|
|
@ -337,6 +338,73 @@ validate_identityref(cxobj *xt,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Validate an RPC node
|
||||
* @param[in] xt XML node to be validated
|
||||
* @retval 1 Validation OK
|
||||
* @retval 0 Validation failed
|
||||
* @retval -1 Error
|
||||
* rfc7950
|
||||
* 7.14.2
|
||||
* If a leaf in the input tree has a "mandatory" statement with the
|
||||
* value "true", the leaf MUST be present in an RPC invocation.
|
||||
*
|
||||
* If a leaf in the input tree has a default value, the server MUST use
|
||||
* this value in the same cases as those described in Section 7.6.1. In
|
||||
* these cases, the server MUST operationally behave as if the leaf was
|
||||
* present in the RPC invocation with the default value as its value.
|
||||
*
|
||||
* If a leaf-list in the input tree has one or more default values, the
|
||||
* server MUST use these values in the same cases as those described in
|
||||
* Section 7.7.2. In these cases, the server MUST operationally behave
|
||||
* as if the leaf-list was present in the RPC invocation with the
|
||||
* default values as its values.
|
||||
*
|
||||
* Since the input tree is not part of any datastore, all "config"
|
||||
* statements for nodes in the input tree are ignored.
|
||||
*
|
||||
* If any node has a "when" statement that would evaluate to "false",
|
||||
* then this node MUST NOT be present in the input tree.
|
||||
*
|
||||
* 7.14.4
|
||||
* Input parameters are encoded as child XML elements to the rpc node's
|
||||
* XML element, in the same order as they are defined within the "input"
|
||||
* statement.
|
||||
*
|
||||
* If the RPC operation invocation succeeded and no output parameters
|
||||
* are returned, the <rpc-reply> contains a single <ok/> element defined
|
||||
* in [RFC6241]. If output parameters are returned, they are encoded as
|
||||
* child elements to the <rpc-reply> element defined in [RFC6241], in
|
||||
* the same order as they are defined within the "output" statement.
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_rpc(cxobj *xrpc)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yn=NULL; /* rpc name */
|
||||
cxobj *xn; /* rpc name */
|
||||
yang_stmt *yi=NULL; /* input name */
|
||||
cxobj *xi; /* input name */
|
||||
|
||||
assert(strcmp(xml_name(xrpc), "rpc")==0);
|
||||
xn = NULL;
|
||||
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
|
||||
if ((yn = xml_spec(xn)) == NULL)
|
||||
goto fail;
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) {
|
||||
if ((yi = xml_spec(xi)) == NULL)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
// ok: /* pass validation */
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Validate a single XML node with yang specification for added entry
|
||||
* 1. Check if mandatory leafs present as subs.
|
||||
* 2. Check leaf values, eg int ranges and string regexps.
|
||||
|
|
@ -418,9 +486,14 @@ xml_yang_validate_add(cxobj *xt,
|
|||
/*! Validate a single XML node with yang specification for all (not only added) entries
|
||||
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
|
||||
* @param[in] xt XML node to be validated
|
||||
* @retval 0 Valid OK
|
||||
* @param[in] arg Not used
|
||||
* @retval -1 Validation failed
|
||||
* @retval 0 Validation OK
|
||||
* @see xml_yang_validate_add
|
||||
* @code
|
||||
* if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0)
|
||||
* err;
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_yang_validate_all(cxobj *xt,
|
||||
|
|
@ -1397,41 +1470,133 @@ xml_non_config_data(cxobj *xt,
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Add yang specification backpoint to XML node
|
||||
/*! Add yang specification backpointer to rpc
|
||||
*
|
||||
* @param[in] xt XML tree node
|
||||
* @param[in] arg Yang spec
|
||||
* @note This may be unnecessary if yspec us set on creation
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @note This may be unnecessary if yspec is set on creation
|
||||
* @note For subs to anyxml nodes will not have spec set
|
||||
* @note No validation is done,... XXX
|
||||
* @code
|
||||
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
|
||||
* @endcode
|
||||
* @see xml_spec_populate
|
||||
*/
|
||||
int
|
||||
xml_spec_populate_rpc(clicon_handle h,
|
||||
cxobj *xrpc,
|
||||
yang_spec *yspec)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *y=NULL; /* yang node */
|
||||
yang_stmt *ymod=NULL; /* yang module */
|
||||
yang_stmt *yi = NULL; /* input */
|
||||
yang_stmt *ya = NULL; /* arg */
|
||||
cxobj *x;
|
||||
cxobj *xi;
|
||||
int i;
|
||||
|
||||
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
|
||||
clicon_err(OE_UNIX, EINVAL, "RPC expected");
|
||||
goto done;
|
||||
}
|
||||
x = NULL;
|
||||
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
|
||||
if (ys_module_by_xml(yspec, x, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod != NULL)
|
||||
y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x));
|
||||
/* Loose semantics: loop through all modules to find the node
|
||||
*/
|
||||
if (y == NULL &&
|
||||
clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){
|
||||
for (i=0; i<yspec->yp_len; i++){
|
||||
ymod = yspec->yp_stmt[i];
|
||||
if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (y){
|
||||
xml_spec_set(x, y);
|
||||
if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){
|
||||
xi = NULL;
|
||||
while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) {
|
||||
if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL)
|
||||
xml_spec_set(xi, ya);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Add yang specification backpointer to XML node
|
||||
* @param[in] xt XML tree node
|
||||
* @param[in] arg Yang spec
|
||||
* @note This may be unnecessary if yspec is set on creation
|
||||
* @note For subs to anyxml nodes will not have spec set
|
||||
* @note No validation is done,... XXX
|
||||
* @note relies on kludge _CLICON_XML_NS_ITERATE
|
||||
* @code
|
||||
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_spec_populate(cxobj *x,
|
||||
void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_spec *yspec = (yang_spec*)arg;
|
||||
// clicon_handle h = (clicon_handle)arg;
|
||||
yang_spec *yspec=NULL; /* yang spec */
|
||||
yang_stmt *y=NULL; /* yang node */
|
||||
yang_stmt *yparent; /* yang parent */
|
||||
yang_stmt *ymod; /* yang module */
|
||||
cxobj *xp; /* xml parent */
|
||||
char *name;
|
||||
int i;
|
||||
|
||||
if (xml_child_spec(xml_name(x), xml_parent(x), yspec, &y) < 0)
|
||||
goto done;
|
||||
#if 0
|
||||
if ((xp = xml_parent(x)) != NULL &&
|
||||
(yp = xml_spec(xp)) != NULL)
|
||||
y = yang_find_datanode((yang_node*)yp, xml_name(x));
|
||||
else
|
||||
y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
|
||||
#endif
|
||||
if (y)
|
||||
yspec = (yang_spec*)arg;
|
||||
if (xml_spec(x))
|
||||
goto ok;
|
||||
xp = xml_parent(x);
|
||||
name = xml_name(x);
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL)
|
||||
y = yang_find_datanode((yang_node*)yparent, name);
|
||||
else if (yspec){
|
||||
if (ys_module_by_xml(yspec, x, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod != NULL)
|
||||
y = yang_find_datanode((yang_node*)ymod, name);
|
||||
/* Loose semantics: loop through all modules to find the node
|
||||
* XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE")
|
||||
*/
|
||||
if (y == NULL && _CLICON_XML_NS_ITERATE){
|
||||
for (i=0; i<yspec->yp_len; i++){
|
||||
ymod = yspec->yp_stmt[i];
|
||||
if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (y)
|
||||
xml_spec_set(x, y);
|
||||
#if 0 /* Add if you want validation error */
|
||||
else {
|
||||
clicon_err(OE_YANG, ENOENT, "No yang top found?");
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/*! Translate from restconf api-path in cvv form to xml xpath
|
||||
* eg a/b=c -> a/[b=c]
|
||||
* @param[in] yspec Yang spec
|
||||
|
|
@ -1757,7 +1922,6 @@ api_path2xml(char *api_path,
|
|||
* @retval 0 OK. If reason is set, Yang error
|
||||
* @retval -1 Error
|
||||
* Assume x0 and x1 are same on entry and that y is the spec
|
||||
* @see put in clixon_keyvalue.c
|
||||
*/
|
||||
static int
|
||||
xml_merge1(cxobj *x0,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@
|
|||
#include "clixon_err.h"
|
||||
#include "clixon_log.h"
|
||||
#include "clixon_string.h"
|
||||
|
||||
#include "clixon_queue.h"
|
||||
#include "clixon_hash.h"
|
||||
#include "clixon_handle.h"
|
||||
|
|
@ -73,7 +72,6 @@
|
|||
*/
|
||||
int xml_child_sort = 1;
|
||||
|
||||
|
||||
/*! Given a child name and an XML object, return yang stmt of child
|
||||
* If no xml parent, find root yang stmt matching name
|
||||
* @param[in] x Child
|
||||
|
|
@ -81,6 +79,8 @@ int xml_child_sort = 1;
|
|||
* @param[in] yspec Yang specification (top level)
|
||||
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
|
||||
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
|
||||
* @note works for import prefix, but not work for generic XML parsing where
|
||||
* xmlns and xmlns:ns are used.
|
||||
*/
|
||||
int
|
||||
xml_child_spec(char *name,
|
||||
|
|
@ -88,17 +88,40 @@ xml_child_spec(char *name,
|
|||
yang_spec *yspec,
|
||||
yang_stmt **yresult)
|
||||
{
|
||||
yang_stmt *y; /* result yang node */
|
||||
int retval = -1;
|
||||
yang_stmt *y = NULL; /* result yang node */
|
||||
yang_stmt *yparent; /* parent yang */
|
||||
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL)
|
||||
y = yang_find_datanode((yang_node*)yparent, name);
|
||||
else if (yspec)
|
||||
y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
|
||||
yang_stmt *ymod = NULL;
|
||||
yang_stmt *yi;
|
||||
int i;
|
||||
|
||||
if (xp && (yparent = xml_spec(xp)) != NULL){
|
||||
if (yparent->ys_keyword == Y_RPC){
|
||||
if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL)
|
||||
y = yang_find_datanode((yang_node*)yi, name);
|
||||
}
|
||||
else
|
||||
y = yang_find_datanode((yang_node*)yparent, name);
|
||||
}
|
||||
else if (yspec){
|
||||
if (ys_module_by_xml(yspec, xp, &ymod) < 0)
|
||||
goto done;
|
||||
if (ymod != NULL)
|
||||
y = yang_find_schemanode((yang_node*)ymod, name);
|
||||
if (y == NULL && _CLICON_XML_NS_ITERATE){
|
||||
for (i=0; i<yspec->yp_len; i++){
|
||||
ymod = yspec->yp_stmt[i];
|
||||
if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
y = NULL;
|
||||
*yresult = y;
|
||||
return 0;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Help function to qsort for sorting entries in xml child vector
|
||||
|
|
|
|||
|
|
@ -658,15 +658,18 @@ yang_find_schemanode(yang_node *yn,
|
|||
return ysmatch;
|
||||
}
|
||||
|
||||
/*! Find first matching data node in all (sub)modules in a yang spec
|
||||
/*! Find first matching data node in all modules in a yang spec (prefixes)
|
||||
*
|
||||
* @param[in] ysp Yang specification
|
||||
* @param[in] argument Name of node. If NULL match first
|
||||
* @param[in] nodeid Name of node. If NULL match first
|
||||
* @param[in] class See yang_class for class of yang nodes
|
||||
* A yang specification has modules as children which in turn can have
|
||||
* syntax-nodes as children. This function goes through all the modules to
|
||||
* look for nodes. Note that if a child to a module is a choice,
|
||||
* the search is made recursively made to the choice's children.
|
||||
* @note works for import prefix, but not work for generic XML parsing where
|
||||
* xmlns and xmlns:ns are used.
|
||||
* @see yang_find_top_ns
|
||||
*/
|
||||
yang_stmt *
|
||||
yang_find_topnode(yang_spec *ysp,
|
||||
|
|
@ -677,7 +680,7 @@ yang_find_topnode(yang_spec *ysp,
|
|||
yang_stmt *yres = NULL; /* result */
|
||||
char *prefix = NULL;
|
||||
char *id = NULL;
|
||||
int i;
|
||||
int i;
|
||||
|
||||
if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
|
||||
goto done;
|
||||
|
|
@ -719,7 +722,7 @@ yang_find_topnode(yang_spec *ysp,
|
|||
}
|
||||
|
||||
/*! Given a yang statement, find the prefix associated to this module
|
||||
* @param[in] ys Yang statement
|
||||
* @param[in] ys Yang statement in module tree (or module itself)
|
||||
* @retval NULL Not found
|
||||
* @retval prefix Prefix as char* pointer into yang tree
|
||||
* @code
|
||||
|
|
@ -745,6 +748,34 @@ yang_find_myprefix(yang_stmt *ys)
|
|||
return prefix;
|
||||
}
|
||||
|
||||
/*! Given a yang statement, find the namespace URI associated to this module
|
||||
* @param[in] ys Yang statement in module tree (or module itself)
|
||||
* @retval NULL Not found
|
||||
* @retval namespace Namspace URI as char* pointer into yang tree
|
||||
* @code
|
||||
* char *myns = yang_find_mynamespace(ys);
|
||||
* @endcode
|
||||
* @see yang_find_module_by_namespace
|
||||
*/
|
||||
char *
|
||||
yang_find_mynamespace(yang_stmt *ys)
|
||||
{
|
||||
yang_stmt *ymod; /* My module */
|
||||
yang_stmt *ynamespace;
|
||||
char *namespace = NULL;
|
||||
|
||||
if ((ymod = ys_module(ys)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "My yang module not found");
|
||||
goto done;
|
||||
}
|
||||
if ((ynamespace = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) == NULL)
|
||||
goto done;
|
||||
namespace = ynamespace->ys_argument;
|
||||
done:
|
||||
return namespace;
|
||||
}
|
||||
|
||||
|
||||
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
|
||||
* @retval 0 not found
|
||||
* @retval 1 found
|
||||
|
|
@ -811,7 +842,52 @@ yang_key2str(int keyword)
|
|||
return (char*)clicon_int2str(ykmap, keyword);
|
||||
}
|
||||
|
||||
/*! Find top module or sub-module given a statement.
|
||||
/*! Find top data node among all modules by namespace in xml tree
|
||||
* @param[in] ysp Yang specification
|
||||
* @param[in] xt XML node
|
||||
* @param[out] ymod Yang module (NULL if not found)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @note works for xml namespaces (xmlns / xmlns:ns)
|
||||
*/
|
||||
int
|
||||
ys_module_by_xml(yang_spec *ysp,
|
||||
cxobj *xt,
|
||||
yang_stmt **ymodp)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ym = NULL; /* module */
|
||||
char *prefix = NULL;
|
||||
char *namespace = NULL; /* namespace URI */
|
||||
|
||||
if (ymodp)
|
||||
*ymodp = NULL;
|
||||
prefix = xml_namespace(xt);
|
||||
if (prefix){
|
||||
/* Get namespace for prefix */
|
||||
if (xml2ns(xt, prefix, &namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
/* Get default namespace */
|
||||
if (xml2ns(xt, NULL, &namespace) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* No namespace found, give up */
|
||||
if (namespace == NULL)
|
||||
goto ok;
|
||||
/* We got the namespace, now get the module */
|
||||
ym = yang_find_module_by_namespace(ysp, namespace);
|
||||
/* Set result param */
|
||||
if (ymodp && ym)
|
||||
*ymodp = ym;
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Find the top module or sub-module given a statement from within a yang tree
|
||||
* Ultimate top is yang spec, dont return that
|
||||
* The routine recursively finds ancestors.
|
||||
* @param[in] ys Any yang statement in a yang tree
|
||||
|
|
@ -840,7 +916,7 @@ ys_module(yang_stmt *ys)
|
|||
return ys;
|
||||
}
|
||||
|
||||
/*! Find top of tree, the yang specification
|
||||
/*! Find top of tree, the yang specification from within the tree
|
||||
* @param[in] ys Any yang statement in a yang tree
|
||||
* @retval yspec The top yang specification
|
||||
* @see ys_module
|
||||
|
|
@ -1005,6 +1081,29 @@ yang_find_module_by_prefix(yang_stmt *ys,
|
|||
return ymod;
|
||||
}
|
||||
|
||||
/*! Given a yang statement and a namespace, return yang module
|
||||
*
|
||||
* @param[in] yspec A yang specification
|
||||
* @param[in] namespace namespace
|
||||
* @retval ymod Yang module statement if found
|
||||
* @retval NULL not found
|
||||
*/
|
||||
yang_stmt *
|
||||
yang_find_module_by_namespace(yang_spec *yspec,
|
||||
char *namespace)
|
||||
{
|
||||
yang_stmt *ymod = NULL;
|
||||
|
||||
if (namespace == NULL)
|
||||
goto done;
|
||||
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
|
||||
if (yang_find((yang_node*)ymod, Y_NAMESPACE, namespace) != NULL)
|
||||
break;
|
||||
}
|
||||
done:
|
||||
return ymod;
|
||||
}
|
||||
|
||||
/*! string is quoted if it contains space or tab, needs double '' */
|
||||
static int inline
|
||||
quotedstring(char *s)
|
||||
|
|
@ -1984,6 +2083,7 @@ yang_parse_filename(const char *filename,
|
|||
int fd = -1;
|
||||
struct stat st;
|
||||
|
||||
// clicon_debug(1, "%s %s", __FUNCTION__, filename);
|
||||
if (stat(filename, &st) < 0){
|
||||
clicon_err(OE_YANG, errno, "%s not found", filename);
|
||||
goto done;
|
||||
|
|
@ -2903,6 +3003,8 @@ yang_spec_load_dir(clicon_handle h,
|
|||
len = b-base;
|
||||
else
|
||||
len = strlen(base);
|
||||
/* remove duplicates: there may be cornercases that dont work, eg
|
||||
* mix of revisions and not? */
|
||||
for (j = (i+1); j < ndp; j++)
|
||||
if (strncmp(base, dp[j].d_name, len) == 0)
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue