* Top-level default leafs assigned.
* Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values. * NACM default behaviour is read-only (empty configs are dead-lockedd) * This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal` * Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111)
This commit is contained in:
parent
65733ffe69
commit
794d51a365
30 changed files with 593 additions and 167 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
|
@ -42,6 +42,34 @@ Expected: July 2020
|
|||
|
||||
### API changes on existing protocol/config features (For users)
|
||||
|
||||
* Top-level default leafs assigned.
|
||||
* Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values.
|
||||
* In this process non-presence containers may be created.
|
||||
* See also [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
|
||||
* NACM default behaviour is read-only (empty configs are dead-lockedd)
|
||||
* This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal`
|
||||
* Due to the previous bult (top-level default leafs)
|
||||
* This means that empty configs or empty NACM configs are not writable (deadlocked).
|
||||
* Workarounds:
|
||||
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
|
||||
|
||||
* This means that empty configs or empty NACM configs are not writable (deadlocked).
|
||||
* Workarounds:
|
||||
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
|
||||
|
||||
* This means that empty configs or empty NACM configs are not writable (deadlocked).
|
||||
* Workarounds:
|
||||
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
|
||||
|
||||
* This means that empty configs or empty NACM configs are not writable (deadlocked).
|
||||
* Workarounds:
|
||||
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
|
||||
|
||||
* This means that empty configs or empty NACM configs are not writable (deadlocked).
|
||||
* Workarounds:
|
||||
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
|
||||
2. Edit the startup-db with a valid NACM config and restart the system
|
||||
3. Set clixon option CLICON_NACM_DISABLED_ON_EMPTY to true which means that if the config is (completely) empty, you can add a NACM config as a first edit.
|
||||
* Netconf lock/unlock behaviour changed to adhere to RFC 6241
|
||||
* Changed commit lock error tag from "lock denied" to "in-use".
|
||||
* Changed unlock error message from "lock is already held" to #lock not active" or "lock held by other session".
|
||||
|
|
@ -53,6 +81,7 @@ Expected: July 2020
|
|||
* CLICON_SSL_SERVER_CERT
|
||||
* CLICON_SSL_SERVER_KEY
|
||||
* CLICON_SSL_CA_CERT
|
||||
* Added CLICON_NACM_DISABLED_ON_EMPTY to mitigate read-only "dead-lock" of empty startup configs.
|
||||
* Restconf FCGI (eg via nginx) have changed reply message syntax slightly as follows (due to refactoring and common code with evhtp):
|
||||
* Bodies in error retuns including html code have been removed
|
||||
* Some (extra) CRLF:s have been removed
|
||||
|
|
@ -99,6 +128,8 @@ Expected: July 2020
|
|||
|
||||
### Corrected Bugs
|
||||
|
||||
* Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
|
||||
* See also API changes since this changes NACM behavior for example.
|
||||
* Fixed: Don't call upgrade callbacks if no revision defined so there's no way to determine right way 'from' and 'to'
|
||||
* Fixed: [lock candidate succeeded even though it is modified #110](https://github.com/clicon/clixon/issues/110)
|
||||
* Fixed: [Need to add the possibility to use anchors around patterns #51](https://github.com/clicon/cligen/issues/51):
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ startup_extraxml(clicon_handle h,
|
|||
/* Clear tmp db */
|
||||
if (xmldb_db_reset(h, tmp_db) < 0)
|
||||
goto done;
|
||||
/* Application may define extra xml in its reset function*/
|
||||
/* Application may define extra xml in its reset function */
|
||||
if (clixon_plugin_reset_all(h, tmp_db) < 0)
|
||||
goto done;
|
||||
/* Extra XML can also be added via file */
|
||||
|
|
@ -238,13 +238,13 @@ startup_extraxml(clicon_handle h,
|
|||
goto fail;
|
||||
}
|
||||
/*
|
||||
* Check if tmp db is empty.
|
||||
* Check if tmp db is empty. XXX no this is not possible.
|
||||
* It should be empty if extra-xml is null and reset plugins did nothing
|
||||
* then skip validation.
|
||||
*/
|
||||
if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0)
|
||||
goto done;
|
||||
if (xt0==NULL || xml_child_nr(xt0)==0)
|
||||
if (xmldb_empty_get(h, tmp_db))
|
||||
goto ok;
|
||||
xt = NULL;
|
||||
/* Validate the tmp db and return possibly upgraded xml in xt
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
typedef struct {
|
||||
uint32_t de_id; /* session id */
|
||||
cxobj *de_xml; /* cache */
|
||||
int de_modified;
|
||||
int de_modified; /* Dirty since loaded/copied/committed/etc XXX:nocache? */
|
||||
int de_empty; /* Empty on read from file, xmldb_readfile and xmldb_put sets it */
|
||||
} db_elmnt;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ cxobj *xmldb_cache_get(clicon_handle h, const char *db);
|
|||
|
||||
int xmldb_modified_get(clicon_handle h, const char *db);
|
||||
int xmldb_modified_set(clicon_handle h, const char *db, int value);
|
||||
|
||||
int xmldb_empty_get(clicon_handle h, const char *db);
|
||||
int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt);
|
||||
|
||||
#endif /* _CLIXON_DATASTORE_H */
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
|
|||
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
|
||||
int xml_default(cxobj *x);
|
||||
int xml_default_recurse(cxobj *xn);
|
||||
int xml_default_yspec(yang_stmt *yspec, cxobj *xn);
|
||||
int xml_nopresence_default(cxobj *xt);
|
||||
int xml_nopresence_default_mark(cxobj *x, void *arg);
|
||||
int xml_sanity(cxobj *x, void *arg);
|
||||
int xml_non_config_data(cxobj *xt, void *arg);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,13 @@
|
|||
|
||||
/* Struct containing module state differences between two modules or two
|
||||
* revisions of same module.
|
||||
* This is in state of flux so it needs to be contained and easily changed.
|
||||
* The most significant usecase is one module-state is a loaded datastore and the other
|
||||
* is the one loaded by the server by its YANG files.
|
||||
*/
|
||||
typedef struct {
|
||||
int md_status; /* 0 if no module-state in a datastore, 1 if there is */
|
||||
char *md_set_id; /* server-specific identifier */
|
||||
cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
|
||||
cxobj *md_diff; /* yang module state containing revisions and XML_FLAG_ADD|DEL|CHANGE */
|
||||
} modstate_diff_t;
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -636,12 +636,11 @@ clicon_argv_set(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Get xml database element including pid and xml cache
|
||||
/*! Get xml database element including id, xml cache, empty on startup and dirty bit
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database
|
||||
* @retval de Database element
|
||||
* @retval NULL None found
|
||||
* @note these use db_elmnt hash, not data
|
||||
*/
|
||||
db_elmnt *
|
||||
clicon_db_elmnt_get(clicon_handle h,
|
||||
|
|
@ -655,14 +654,12 @@ clicon_db_elmnt_get(clicon_handle h,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*! Set xml database element including pid and xml cache
|
||||
/*! Set xml database element including id, xml cache, empty on startup and dirty bit
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Name of database
|
||||
* @param[in] de Database element
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* XXX add prefix to db to ensure uniqueness?
|
||||
* @note these use db_elmnt hash, not data
|
||||
*/
|
||||
int
|
||||
clicon_db_elmnt_set(clicon_handle h,
|
||||
|
|
|
|||
|
|
@ -518,6 +518,26 @@ xmldb_modified_get(clicon_handle h,
|
|||
return de->de_modified;
|
||||
}
|
||||
|
||||
/*! Get empty flag from datastore (the datastore was empty ON LOAD)
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Database name
|
||||
* @retval -1 Error (datastore does not exist)
|
||||
* @retval 0 Db was not empty on load
|
||||
* @retval 1 Db was empty on load
|
||||
*/
|
||||
int
|
||||
xmldb_empty_get(clicon_handle h,
|
||||
const char *db)
|
||||
{
|
||||
db_elmnt *de;
|
||||
|
||||
if ((de = clicon_db_elmnt_get(h, db)) == NULL){
|
||||
clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
|
||||
return -1;
|
||||
}
|
||||
return de->de_empty;
|
||||
}
|
||||
|
||||
/*! Get modified flag from datastore
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] db Database name
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
#include "clixon_xpath.h"
|
||||
#include "clixon_json.h"
|
||||
#include "clixon_nacm.h"
|
||||
#include "clixon_path.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_xml_map.h"
|
||||
|
|
@ -104,7 +105,7 @@ singleconfigroot(cxobj *xt,
|
|||
}
|
||||
}
|
||||
if (i != 1){
|
||||
clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
|
||||
clicon_err(OE_DB, ENOENT, "Top-element is not unique, expecting single config");
|
||||
goto done;
|
||||
}
|
||||
x = NULL;
|
||||
|
|
@ -443,33 +444,63 @@ text_read_modstate(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
disable_nacm_on_empty(cxobj *x0,
|
||||
yang_stmt *yspec)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ymod;
|
||||
cxobj **vec = NULL;
|
||||
int len = 0;
|
||||
cxobj *xb;
|
||||
|
||||
if ((ymod = yang_find(yspec, Y_MODULE, "ietf-netconf-acm")) == NULL)
|
||||
goto ok;
|
||||
if (clixon_xml_find_instance_id(x0, yspec, &vec, &len, "/nacm:nacm/nacm:enable-nacm") < 1)
|
||||
goto done;
|
||||
if (len){
|
||||
if ((xb = xml_body_get(vec[0])) == NULL)
|
||||
goto done;
|
||||
if (xml_value_set(xb, "false") < 0)
|
||||
goto done;
|
||||
}
|
||||
if (vec)
|
||||
free(vec);
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Common read function that reads an XML tree from file
|
||||
* @param[in] th Datastore text handle
|
||||
* @param[in] db Symbolic database name, eg "candidate", "running"
|
||||
* @param[in] yb How to bind yang to XML top-level when parsing
|
||||
* @param[in] yspec Top-level yang spec
|
||||
* @param[out] xp XML tree read from file
|
||||
* @param[out] de If set, return db-element status (eg empty flag)
|
||||
* @param[out] msdiff If set, return modules-state differences
|
||||
* @retval -1 Error
|
||||
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
|
||||
* @retval 1 OK
|
||||
* @note retval 0 is NYI becaues of functions calling this function
|
||||
* @note retval 0 is NYI because of functions calling this function cannot handle it yet
|
||||
*/
|
||||
#undef XMLDB_READFILE_FAIL
|
||||
#undef XMLDB_READFILE_FAIL /* See comment on retval = 0 above */
|
||||
int
|
||||
xmldb_readfile(clicon_handle h,
|
||||
const char *db,
|
||||
yang_bind yb,
|
||||
yang_stmt *yspec,
|
||||
cxobj **xp,
|
||||
db_elmnt *de,
|
||||
modstate_diff_t *msdiff)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *x0 = NULL;
|
||||
char *dbfile = NULL;
|
||||
int fd = -1;
|
||||
char *format;
|
||||
int ret;
|
||||
int retval = -1;
|
||||
cxobj *x0 = NULL;
|
||||
char *dbfile = NULL;
|
||||
int fd = -1;
|
||||
char *format;
|
||||
int ret;
|
||||
|
||||
if (xmldb_db2file(h, db, &dbfile) < 0)
|
||||
goto done;
|
||||
|
|
@ -497,8 +528,9 @@ xmldb_readfile(clicon_handle h,
|
|||
goto fail;
|
||||
#endif
|
||||
/* Always assert a top-level called "config".
|
||||
To ensure that, deal with two cases:
|
||||
1. File is empty <top/> -> rename top-level to "config" */
|
||||
* To ensure that, deal with two cases:
|
||||
* 1. File is empty <top/> -> rename top-level to "config"
|
||||
*/
|
||||
if (xml_child_nr(x0) == 0){
|
||||
if (xml_name_set(x0, "config") < 0)
|
||||
goto done;
|
||||
|
|
@ -509,7 +541,10 @@ xmldb_readfile(clicon_handle h,
|
|||
if (singleconfigroot(x0, &x0) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* From Clixon 3.10,datastore files may contain module-state defining
|
||||
if (xml_child_nr(x0) == 0 && de)
|
||||
de->de_empty = 1;
|
||||
|
||||
/* Datastore files may contain module-state defining
|
||||
* which modules are used in the file.
|
||||
*/
|
||||
if (text_read_modstate(h, yspec, x0, msdiff) < 0)
|
||||
|
|
@ -569,16 +604,36 @@ xmldb_get_nocache(clicon_handle h,
|
|||
size_t xlen;
|
||||
int i;
|
||||
int ret;
|
||||
db_elmnt de0 = {0,};
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
goto done;
|
||||
}
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
|
||||
|
||||
/* Check if empty */
|
||||
if (xml_child_nr(xt) == 0)
|
||||
empty = 1;
|
||||
/* Add global defaults.
|
||||
* Must do it before xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, xt) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(xt, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
/* Given the xpath, return a vector of matches in xvec */
|
||||
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
|
|
@ -664,6 +719,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
cxobj *x1t = NULL;
|
||||
db_elmnt de0 = {0,};
|
||||
int ret;
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
|
|
@ -672,7 +728,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
de = clicon_db_elmnt_get(h, db);
|
||||
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
||||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -680,10 +736,30 @@ xmldb_get_cache(clicon_handle h,
|
|||
* No, argument against: we may want to have a semantically wrong file and wish to edit?
|
||||
*/
|
||||
de0.de_xml = x0t;
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
|
||||
} /* x0t == NULL */
|
||||
else
|
||||
x0t = de->de_xml;
|
||||
|
||||
/* Check if empty, must be before add global defaults */
|
||||
if (xml_child_nr(x0t) == 0)
|
||||
empty = 1;
|
||||
|
||||
/* Add global defaults.
|
||||
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, x0t) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(x0t, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Here x0t looks like: <config>...</config> */
|
||||
/* Given the xpath, return a vector of matches in xvec
|
||||
* Can we do everything in one go?
|
||||
|
|
@ -693,8 +769,6 @@ xmldb_get_cache(clicon_handle h,
|
|||
* a) for every node that is found, copy to new tree
|
||||
* b) if config dont dont state data
|
||||
*/
|
||||
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
||||
|
|
@ -703,6 +777,7 @@ xmldb_get_cache(clicon_handle h,
|
|||
goto done;
|
||||
xml_spec_set(x1t, xml_spec(x0t));
|
||||
|
||||
|
||||
if (xlen < 1000){
|
||||
/* This is optimized for the case when the tree is large and xlen is small
|
||||
* If the tree is large and xlen too, then the other is better.
|
||||
|
|
@ -786,6 +861,7 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
db_elmnt *de = NULL;
|
||||
db_elmnt de0 = {0,};
|
||||
int ret;
|
||||
int empty = 0;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
|
|
@ -794,7 +870,7 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
de = clicon_db_elmnt_get(h, db);
|
||||
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
|
||||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -806,6 +882,25 @@ xmldb_get_zerocopy(clicon_handle h,
|
|||
} /* x0t == NULL */
|
||||
else
|
||||
x0t = de->de_xml;
|
||||
/* Check if empty, must be before add global defaults */
|
||||
if (xml_child_nr(x0t) == 0)
|
||||
empty = 1;
|
||||
|
||||
/* Add global defaults.
|
||||
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
|
||||
*/
|
||||
if (xml_default_yspec(yspec, x0t) < 0)
|
||||
goto done;
|
||||
/* If empty database, then disable NACM if loaded
|
||||
* This has some drawbacks. One is that a config may become empty at a later stage
|
||||
* and then this does not hold.
|
||||
*/
|
||||
if (empty &&
|
||||
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
|
||||
if (disable_nacm_on_empty(x0t, yspec) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Here xt looks like: <config>...</config> */
|
||||
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||
goto done;
|
||||
|
|
@ -874,7 +969,7 @@ xmldb_get(clicon_handle h,
|
|||
* @param[in] xpath String with XPATH syntax. or NULL for all
|
||||
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache
|
||||
* @param[out] xret Single return XML tree. Free with xml_free()
|
||||
* @param[out] msdiff If set, return modules-state differences (upgrade code)
|
||||
* @param[out] msdiff If set, return modules-state differences (upgrade code)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @code
|
||||
|
|
@ -944,12 +1039,16 @@ xmldb_get0_clear(clicon_handle h,
|
|||
|
||||
if (x == NULL)
|
||||
goto ok;
|
||||
/* clear XML tree of defaults */
|
||||
/* Mark non-presence containers as XML_FLAG_DEFAULT */
|
||||
if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
|
||||
goto done;
|
||||
/* Clear XML tree of defaults */
|
||||
if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
|
||||
goto done;
|
||||
|
||||
/* clear mark and change */
|
||||
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
|
||||
(void*)(0xff));
|
||||
(void*)(0xffff));
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -972,3 +1071,4 @@ xmldb_get0_free(clicon_handle h,
|
|||
*xp = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec, cxobj **xp, modstate_diff_t *msd);
|
||||
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec,
|
||||
cxobj **xp, db_elmnt *de, modstate_diff_t *msd);
|
||||
|
||||
#endif /* _CLIXON_DATASTORE_READ_H */
|
||||
|
|
|
|||
|
|
@ -481,9 +481,11 @@ text_modify(clicon_handle h,
|
|||
switch(op){
|
||||
case OP_CREATE:
|
||||
if (x0){
|
||||
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
if (xml_nopresence_default(x0) == 0){
|
||||
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
case OP_REPLACE: /* fall thru */
|
||||
case OP_MERGE:
|
||||
|
|
@ -928,7 +930,7 @@ xmldb_put(clicon_handle h,
|
|||
/* If there is no xml x0 tree (in cache), then read it from file */
|
||||
if (x0 == NULL){
|
||||
firsttime++; /* to avoid leakage on error, see fail from text_modify */
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, NULL)) < 0)
|
||||
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, de, NULL)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
|
|
@ -987,10 +989,10 @@ xmldb_put(clicon_handle h,
|
|||
db_elmnt de0 = {0,};
|
||||
if (de != NULL)
|
||||
de0 = *de;
|
||||
if (de0.de_xml == NULL){
|
||||
if (de0.de_xml == NULL)
|
||||
de0.de_xml = x0;
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
}
|
||||
de0.de_empty = (xml_child_nr(de0.de_xml) == 0);
|
||||
clicon_db_elmnt_set(h, db, &de0);
|
||||
}
|
||||
if (xmldb_db2file(h, db, &dbfile) < 0)
|
||||
goto done;
|
||||
|
|
@ -1057,6 +1059,9 @@ xmldb_dump(clicon_handle h,
|
|||
char *format;
|
||||
int pretty;
|
||||
|
||||
/* clear XML tree of defaults */
|
||||
if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
|
||||
goto done;
|
||||
/* Add modstate first */
|
||||
if ((x = clicon_modst_cache_get(h, 1)) != NULL){
|
||||
if ((xmodst = xml_dup(x)) == NULL)
|
||||
|
|
@ -1079,3 +1084,4 @@ xmldb_dump(clicon_handle h,
|
|||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1100,7 +1100,9 @@ nacm_access(clicon_handle h,
|
|||
/* Do initial nacm processing common to all access validation in
|
||||
* RFC8341 3.4 */
|
||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
||||
operation is permitted. */
|
||||
* operation is permitted.
|
||||
* note option CLICON_NACM_DISABLED_ON_EMPTY
|
||||
*/
|
||||
if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
|
||||
goto permit;
|
||||
enabled = xml_body(x);
|
||||
|
|
@ -1155,6 +1157,7 @@ nacm_access_pre(clicon_handle h,
|
|||
cxobj *xnacm = NULL;
|
||||
cvec *nsc = NULL;
|
||||
|
||||
/* Check clixon option: disabled, external tree or internal */
|
||||
mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (mode == NULL)
|
||||
goto permit;
|
||||
|
|
|
|||
|
|
@ -1883,6 +1883,7 @@ xml_copy_one(cxobj *x0,
|
|||
default:
|
||||
break;
|
||||
}
|
||||
xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT)); /* Maybe more flags */
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
|
|
|
|||
|
|
@ -979,9 +979,223 @@ xml_namespace_change(cxobj *x,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Add default values (if not set)
|
||||
* @param[in] xt XML tree with some node marked
|
||||
* Typically called in a recursive apply function:
|
||||
int
|
||||
xml_default_create1(yang_stmt *y,
|
||||
cxobj *xt,
|
||||
int top,
|
||||
cxobj **xcp)
|
||||
{
|
||||
int retval = -1;
|
||||
char *namespace;
|
||||
char *prefix;
|
||||
int ret;
|
||||
cxobj *xc = NULL;
|
||||
|
||||
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
xml_spec_set(xc, y);
|
||||
|
||||
/* assign right prefix */
|
||||
if ((namespace = yang_find_mynamespace(y)) != NULL){
|
||||
prefix = NULL;
|
||||
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
|
||||
goto done;
|
||||
if (ret){ /* Namespace found, prefix returned in prefix */
|
||||
if (xml_prefix_set(xc, prefix) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* Namespace does not exist in target, must add it w xmlns attr.
|
||||
use source prefix */
|
||||
if (!top){
|
||||
if ((prefix = yang_find_myprefix(y)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (xml_addsub(xt, xc) < 0)
|
||||
goto done;
|
||||
*xcp = xc;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create leaf from default value
|
||||
*
|
||||
* @param[in] yt Yang spec
|
||||
* @param[in] xt XML tree
|
||||
* @param[in] top Use default namespace (if you create xmlns statement)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_default_create(yang_stmt *y,
|
||||
cxobj *xt,
|
||||
int top)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xc = NULL;
|
||||
cxobj *xb;
|
||||
char *str;
|
||||
|
||||
if (xml_default_create1(y, xt, top, &xc) < 0)
|
||||
goto done;
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
|
||||
goto done;
|
||||
if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
if (xml_value_set(xb, str) < 0)
|
||||
goto done;
|
||||
free(str);
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Try to see if intermediate nodes are necessary for default values, create if so
|
||||
*
|
||||
* @param[in] yt Yang container (no-presence)
|
||||
* @param[out] createp Need to create XML container
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_nopresence_try(yang_stmt *yt,
|
||||
int *createp)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *y;
|
||||
|
||||
if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
|
||||
clicon_err(OE_XML, EINVAL, "yt argument is not container");
|
||||
goto done;
|
||||
}
|
||||
*createp = 0;
|
||||
y = NULL;
|
||||
while ((y = yn_each(yt, y)) != NULL) {
|
||||
switch (yang_keyword_get(y)){
|
||||
case Y_LEAF:
|
||||
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
|
||||
/* Need to create container */
|
||||
*createp = 1;
|
||||
goto ok;
|
||||
}
|
||||
break;
|
||||
case Y_CONTAINER:
|
||||
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
|
||||
/* If this is non-presence, (and it does not exist in xt) call recursively
|
||||
* and create nodes if any default value exist first. Then continue and populate?
|
||||
*/
|
||||
if (xml_nopresence_try(y, createp) < 0)
|
||||
goto done;
|
||||
if (*createp)
|
||||
goto ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} /* switch */
|
||||
}
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Ensure default values are set on (children of) one single xml node
|
||||
*
|
||||
* Not recursive, except in one case with one or several non-presence containers, in which case
|
||||
* XML containers may be created to host default values. That code may be a little too recursive.
|
||||
* @param[in] yt Yang spec
|
||||
* @param[in] xt XML tree (with yt as spec of xt, informally)
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
xml_default1(yang_stmt *yt,
|
||||
cxobj *xt)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *yc;
|
||||
cxobj *xc;
|
||||
int top=0; /* Top symbol (set default namespace) */
|
||||
int create = 0;
|
||||
|
||||
if (xt == NULL){ /* No xml */
|
||||
clicon_err(OE_XML, EINVAL, "No XML argument");
|
||||
goto done;
|
||||
}
|
||||
switch (yang_keyword_get(yt)){
|
||||
case Y_MODULE:
|
||||
case Y_SUBMODULE:
|
||||
top++;
|
||||
case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
|
||||
case Y_LIST:
|
||||
case Y_INPUT:
|
||||
case Y_OUTPUT:
|
||||
yc = NULL;
|
||||
while ((yc = yn_each(yt, yc)) != NULL) {
|
||||
switch (yang_keyword_get(yc)){
|
||||
case Y_LEAF:
|
||||
if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
|
||||
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
|
||||
/* No such child exist, create this leaf */
|
||||
if (xml_default_create(yc, xt, top) < 0)
|
||||
goto done;
|
||||
xml_sort(xt);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Y_CONTAINER:
|
||||
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
|
||||
/* If this is non-presence, (and it does not exist in xt) call
|
||||
* recursively and create nodes if any default value exist first.
|
||||
* Then continue and populate?
|
||||
*/
|
||||
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
|
||||
/* No such container exist, recursively try if needed */
|
||||
if (xml_nopresence_try(yc, &create) < 0)
|
||||
goto done;
|
||||
if (create){
|
||||
/* Retval shows there is a default value need to create the
|
||||
* container */
|
||||
if (xml_default_create1(yc, xt, top, &xc) < 0)
|
||||
goto done;
|
||||
xml_sort(xt);
|
||||
/* Then call it recursively */
|
||||
if (xml_default1(yc, xc) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} /* switch */
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Ensure default values are set on existing leaf children of this node
|
||||
*
|
||||
* Assume yang is bound to the tree
|
||||
* @param[in] xt XML tree
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
|
|
@ -990,79 +1204,22 @@ xml_default(cxobj *xt)
|
|||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ys;
|
||||
yang_stmt *y;
|
||||
// int i; // XXX
|
||||
cxobj *xc;
|
||||
cxobj *xb;
|
||||
char *str;
|
||||
int added=0;
|
||||
char *namespace;
|
||||
char *prefix;
|
||||
int ret;
|
||||
|
||||
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
/* Check leaf defaults */
|
||||
if (yang_keyword_get(ys) == Y_CONTAINER || yang_keyword_get(ys) == Y_LIST ||
|
||||
yang_keyword_get(ys) == Y_INPUT){
|
||||
y = NULL;
|
||||
while ((y = yn_each(ys, y)) != NULL) {
|
||||
if (yang_keyword_get(y) != Y_LEAF)
|
||||
continue;
|
||||
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
|
||||
if (!xml_find(xt, yang_argument_get(y))){
|
||||
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
|
||||
goto done;
|
||||
xml_spec_set(xc, y);
|
||||
|
||||
/* assign right prefix */
|
||||
if ((namespace = yang_find_mynamespace(y)) != NULL){
|
||||
prefix = NULL;
|
||||
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
|
||||
goto done;
|
||||
if (ret){
|
||||
if (xml_prefix_set(xc, prefix) < 0)
|
||||
goto done;
|
||||
}
|
||||
else{ /* namespace does not exist in target, use source prefix */
|
||||
if ((prefix = yang_find_myprefix(y)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "strdup");
|
||||
goto done;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
xml_flag_set(xc, XML_FLAG_DEFAULT);
|
||||
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
|
||||
goto done;
|
||||
if ((str = cv2str_dup(yang_cv_get(y))) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv2str_dup");
|
||||
goto done;
|
||||
}
|
||||
if (xml_value_set(xb, str) < 0)
|
||||
goto done;
|
||||
free(str);
|
||||
added++;
|
||||
if (xml_insert(xt, xc, INS_LAST, NULL, NULL) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xml_default1(ys, xt) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Recursively fill in default values in a tree
|
||||
* Alt to use xml_apply
|
||||
/*! Recursively fill in default values in an XML tree
|
||||
* @param[in] xt XML tree
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
xml_default_recurse(cxobj *xn)
|
||||
|
|
@ -1082,6 +1239,85 @@ xml_default_recurse(cxobj *xn)
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Ensure default values are set on top-level
|
||||
*
|
||||
* Not recursive, except in one case with one or several non-presence containers
|
||||
* @param[in] xt XML tree
|
||||
* Typically called in a recursive apply function:
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
xml_default_yspec(yang_stmt *yspec,
|
||||
cxobj *xt)
|
||||
{
|
||||
int retval = -1;
|
||||
yang_stmt *ymod = NULL;
|
||||
|
||||
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
|
||||
clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
|
||||
goto done;
|
||||
}
|
||||
while ((ymod = yn_each(yspec, ymod)) != NULL)
|
||||
if (xml_default1(ymod, xt) < 0)
|
||||
goto done;
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! This node is a default set value or (recursively) a non-presence container
|
||||
* @retval 1 xt is a nopresence/default node (ie "virtual")
|
||||
* @retval 0 xt is not such a node
|
||||
*/
|
||||
int
|
||||
xml_nopresence_default(cxobj *xt)
|
||||
{
|
||||
cxobj *xc;
|
||||
yang_stmt *yt;
|
||||
|
||||
if ((yt = xml_spec(xt)) == NULL)
|
||||
return 0;
|
||||
switch (yang_keyword_get(yt)){
|
||||
case Y_CONTAINER:
|
||||
if (yang_find(yt, Y_PRESENCE, NULL))
|
||||
return 0;
|
||||
break;
|
||||
case Y_LEAF:
|
||||
return xml_flag(xt, XML_FLAG_DEFAULT)?1:0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
xc = NULL;
|
||||
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
|
||||
if (xml_nopresence_default(xc) == 0)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*! Remove xml container if it is non-presence and only contains default leafs
|
||||
* Called from xml_apply. Reason for marking is to delete it afterwords.
|
||||
* @param[in] x
|
||||
* @param[in] arg (flag value)
|
||||
* @code
|
||||
* if (xml_apply(xt, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
|
||||
* err;
|
||||
* if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
|
||||
* goto done;
|
||||
* @endcode
|
||||
*/
|
||||
int
|
||||
xml_nopresence_default_mark(cxobj *x,
|
||||
void *arg)
|
||||
{
|
||||
if (xml_nopresence_default(x))
|
||||
xml_flag_set(x, (intptr_t)arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
|
||||
* @param[in] xt XML top of tree
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1029,6 +1029,7 @@ xml_insert2(cxobj *xp,
|
|||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
|
||||
* @note It is assumed that all siblings of xi are YANG bound
|
||||
*/
|
||||
int
|
||||
xml_insert(cxobj *xp,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
# Authentication and authorization and IETF NACM
|
||||
# See RFC 8341 A.2
|
||||
# But replaced ietf-netconf-monitoring with *
|
||||
# Note credenials check set to none since USER poses as different users.
|
||||
# Note:
|
||||
# 1. credenials check set to none since USER poses as different users.
|
||||
# 2. CLICON_NACM_DISABLE_ON_EMPTY: start with empty config and add nacm config
|
||||
|
||||
# Magic line must be first in script (see README.md)
|
||||
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
|
||||
|
|
@ -31,6 +33,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
@ -140,7 +143,7 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/da
|
|||
|
||||
# explicitly disable nacm (regression on netgate bug)
|
||||
new "disable nacm"
|
||||
expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 201 Created"
|
||||
expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
new "auth set authentication config"
|
||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
<CLICON_NACM_CREDENTIALS>$mode</CLICON_NACM_CREDENTIALS>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
|
@ -62,7 +63,6 @@ EOF
|
|||
# 6: expected return value of test2
|
||||
# 7: expected return value of test3
|
||||
# 8: startup mode: startup or init
|
||||
# 9: Dont set default values (nullify them)
|
||||
testrun(){
|
||||
enablenacm=$1
|
||||
readdefault=$2
|
||||
|
|
@ -72,24 +72,9 @@ testrun(){
|
|||
ret2=$6
|
||||
ret3=$7
|
||||
db=$8
|
||||
nulldef=$9
|
||||
|
||||
# Set default values (or not)
|
||||
if [ $nulldef -ne 0 ]; then
|
||||
# Defaults should be: true permit deny permit:
|
||||
# nacm enabled, exec default permit, read permit (expect fail)"
|
||||
# which means results should be 0 1 3
|
||||
# Also enable-nacm is present since otherwise the nacm container would be removed
|
||||
# since it is non-presence
|
||||
NACM=$(cat <<EOF
|
||||
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||
<enable-nacm>${enablenacm}</enable-nacm>
|
||||
</nacm>
|
||||
EOF
|
||||
)
|
||||
else
|
||||
NACM=$(cat <<EOF
|
||||
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||
NACM=$(cat <<EOF
|
||||
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||
<enable-nacm>${enablenacm}</enable-nacm>
|
||||
<read-default>${readdefault}</read-default>
|
||||
<write-default>${writedefault}</write-default>
|
||||
|
|
@ -98,7 +83,6 @@ EOF
|
|||
</nacm>
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
# Initial data
|
||||
XML='<x xmlns="urn:example:nacm">42</x>'
|
||||
|
||||
|
|
@ -117,6 +101,7 @@ EOF
|
|||
start_backend -s $db -f $cfg
|
||||
else
|
||||
new "Restart backend as eg follows: -Ff $cfg -s $db"
|
||||
sleep 2
|
||||
fi
|
||||
|
||||
new "waiting"
|
||||
|
|
@ -132,12 +117,16 @@ EOF
|
|||
wait_restconf
|
||||
|
||||
# Use POST (instead of startup)
|
||||
# Note this only works because CLICON_NACM_DISABLED_ON_EMPTY is true
|
||||
if [ $db = init ]; then
|
||||
new "Set Initial data using POST"
|
||||
expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
|
||||
# Must set NACM first
|
||||
new "Set NACM using PATCH"
|
||||
expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" -d "<data>$NACM$XML</data>" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
|
||||
|
||||
# new "Set Initial data using POST"
|
||||
# expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
|
||||
|
||||
|
||||
new "Set NACM using POST"
|
||||
expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
|
||||
fi
|
||||
|
||||
#----------- First get
|
||||
|
|
@ -204,39 +193,36 @@ EOF
|
|||
# Run a lot of tests with different settings of default read/write/exec
|
||||
# Outer loop either starts from startup or inits config via restconf POST
|
||||
for db in startup init; do
|
||||
new "nacm enabled and all defaults permit"
|
||||
testrun true permit permit permit 0 0 0 $db 0
|
||||
new "1. nacm enabled and all defaults permit"
|
||||
testrun true permit permit permit 0 0 0 $db
|
||||
|
||||
new "nacm disabled and all defaults permit"
|
||||
testrun false permit permit permit 0 0 0 $db 0
|
||||
new "2. nacm disabled and all defaults permit"
|
||||
testrun false permit permit permit 0 0 0 $db
|
||||
|
||||
new "nacm disabled and all defaults deny"
|
||||
testrun false deny deny deny 0 0 0 $db 0
|
||||
new "3. nacm disabled and all defaults deny"
|
||||
testrun false deny deny deny 0 0 0 $db
|
||||
|
||||
new "nacm enabled, all defaults deny (expect fail)"
|
||||
testrun true deny deny deny 1 1 1 $db 0
|
||||
new "4. nacm enabled, all defaults deny (expect fail)"
|
||||
testrun true deny deny deny 1 1 1 $db
|
||||
|
||||
new "nacm enabled, exec default deny - read permit (expect fail)"
|
||||
testrun true permit deny deny 1 1 1 $db 0
|
||||
new "5. nacm enabled, exec default deny - read permit (expect fail)"
|
||||
testrun true permit deny deny 1 1 1 $db
|
||||
|
||||
new "nacm enabled, exec default deny - write permit (expect fail)"
|
||||
testrun true deny permit deny 1 1 1 $db 0
|
||||
new "6. nacm enabled, exec default deny - write permit (expect fail)"
|
||||
testrun true deny permit deny 1 1 1 $db
|
||||
|
||||
new "nacm enabled, exec default deny read/write permit (expect fail)"
|
||||
testrun true permit permit deny 1 1 1 $db 0
|
||||
new "7. nacm enabled, exec default deny read/write permit (expect fail)"
|
||||
testrun true permit permit deny 1 1 1 $db
|
||||
|
||||
new "nacm enabled, exec default permit, all others deny (expect fail)"
|
||||
testrun true deny deny permit 2 1 2 $db 0
|
||||
new "8. nacm enabled, exec default permit, all others deny (expect fail)"
|
||||
testrun true deny deny permit 2 1 2 $db
|
||||
|
||||
new "nacm enabled, exec default permit, read permit (expect fail)"
|
||||
testrun true permit deny permit 0 1 3 $db 0 # This is yang default
|
||||
new "9. nacm enabled, exec default permit, read permit (expect fail)"
|
||||
testrun true permit deny permit 0 1 3 $db # This is yang default
|
||||
|
||||
new "nacm enabled, with default values (no settings - should be same as previous)"
|
||||
# note last 1 means nullify all default values)
|
||||
testrun true xxx xxx xxx 0 1 3 init 1
|
||||
new "10. nacm enabled, exec default permit, write permit (expect fail)"
|
||||
testrun true deny permit permit 2 0 2 $db
|
||||
|
||||
new "nacm enabled, exec default permit, write permit (expect fail)"
|
||||
testrun true deny permit permit 2 0 2 $db 0
|
||||
done
|
||||
|
||||
rm -rf $dir
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ module nacm-example{
|
|||
}
|
||||
prefix nacm;
|
||||
container authentication {
|
||||
description "Example code for enabling www basic auth and some example
|
||||
presence "To keep this from auto-expanding";
|
||||
description "Example code for enabling www basic auth and some example
|
||||
users";
|
||||
leaf basic_auth{
|
||||
description "Basic user / password authentication as in HTTP basic auth";
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,6 @@ module scaling{
|
|||
yang-version 1.1;
|
||||
namespace "urn:example:clixon";
|
||||
prefix ex;
|
||||
import "clixon-config" {
|
||||
prefix cc;
|
||||
}
|
||||
container x {
|
||||
list y {
|
||||
key "a";
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ cat <<EOF > $cfg
|
|||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
|
||||
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
|
||||
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
|
||||
</clixon-config>
|
||||
EOF
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ cat <<EOF > $cfg
|
|||
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
|
||||
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
|
||||
</clixon-config>
|
||||
|
||||
EOF
|
||||
|
||||
# Create running-db containin the interface "run" OK
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ module interfaces{
|
|||
reference
|
||||
"RFC 2863: The Interfaces Group MIB";
|
||||
}
|
||||
leaf foo{
|
||||
description "Should not appear";
|
||||
type string;
|
||||
default "bar";
|
||||
}
|
||||
container interfaces {
|
||||
description
|
||||
"Interface configuration parameters.";
|
||||
|
|
@ -52,8 +57,11 @@ module interfaces{
|
|||
leaf description {
|
||||
type string;
|
||||
}
|
||||
|
||||
|
||||
leaf foo{
|
||||
description "Should not appear";
|
||||
type string;
|
||||
default "bar";
|
||||
}
|
||||
leaf type {
|
||||
type string;
|
||||
mandatory true;
|
||||
|
|
@ -124,6 +132,11 @@ module interfaces{
|
|||
reference
|
||||
"RFC 2863: The Interfaces Group MIB";
|
||||
}
|
||||
leaf foo{
|
||||
description "Should not appear";
|
||||
type string;
|
||||
default "fie";
|
||||
}
|
||||
container interfaces {
|
||||
description
|
||||
"Interface configuration parameters.";
|
||||
|
|
@ -133,6 +146,11 @@ module interfaces{
|
|||
leaf name {
|
||||
type string;
|
||||
}
|
||||
leaf foo{
|
||||
description "Should not appear";
|
||||
type string;
|
||||
default "bar";
|
||||
}
|
||||
container docs{
|
||||
description "Original description is wrapped and renamed";
|
||||
leaf descr {
|
||||
|
|
@ -290,7 +308,6 @@ XML='<interfaces xmlns="urn:example:interfaces"><interface><name>e0</name><type>
|
|||
ALL="<config>$MODSTATE$XML</config>"
|
||||
|
||||
# -u means trigger example upgrade
|
||||
new "test params: -s startup -f $cfg -- -u"
|
||||
|
||||
# kill old backend (if any)
|
||||
new "kill old backend"
|
||||
|
|
@ -301,6 +318,7 @@ fi
|
|||
new "start backend -s startup -f $cfg -q -- -u"
|
||||
output=$(sudo $clixon_backend -F -D $DBG -s startup -f $cfg -q -- -u)
|
||||
#echo "$output"
|
||||
|
||||
if [ "$ALL" != "$output" ]; then
|
||||
err "$ALL" "$output"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -46,9 +46,8 @@ module clixon-config {
|
|||
description
|
||||
"Added: CLICON_CLI_LINES_DEFAULT
|
||||
Added enum HIDE to CLICON_CLI_GENMODEL
|
||||
Added CLICON_SSL_SERVER_CERT
|
||||
Added CLICON_SSL_SERVER_KEY
|
||||
Added CLICON_SSL_CA_CERT";
|
||||
Added CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
|
||||
Added CLICON_NACM_DISABLED_ON_EMPTY";
|
||||
}
|
||||
revision 2020-04-23 {
|
||||
description
|
||||
|
|
@ -719,6 +718,20 @@ module clixon-config {
|
|||
exact for example, this user must exist and be used, otherwise
|
||||
another user (such as root or www) can pose as it.";
|
||||
}
|
||||
leaf CLICON_NACM_DISABLED_ON_EMPTY {
|
||||
type boolean;
|
||||
default false;
|
||||
description
|
||||
"RFC 8341 and ietf-netconf-acm@2018-02-14.yang defines enable-nacm as true by
|
||||
default. Since also write-default is deny by default it leads to that empty
|
||||
configs can not be edited.
|
||||
This means that a startup config must always have a NACM configuration or
|
||||
that the NACM recovery session is used to edit an empty config.
|
||||
If this option is set, Clixon disables NACM if a datastore is empty on load.
|
||||
Note that it only makes the check on initial load, not if a store 'becomes'
|
||||
empty, but enables a clixon nacm system to start empty and add an NACM
|
||||
config after boot.";
|
||||
}
|
||||
leaf CLICON_MODULE_LIBRARY_RFC7895 {
|
||||
type boolean;
|
||||
default true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue