* 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:
Olof hagsand 2018-12-16 19:46:26 +01:00
parent e5c0b06cf9
commit ae1af8da9e
63 changed files with 1852 additions and 3492 deletions

View file

@ -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 -

View file

@ -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 */

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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){

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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;