* 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:
Olof hagsand 2020-08-06 15:19:38 +02:00
parent 65733ffe69
commit 794d51a365
30 changed files with 593 additions and 167 deletions

View file

@ -42,6 +42,34 @@ Expected: July 2020
### API changes on existing protocol/config features (For users) ### 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 * Netconf lock/unlock behaviour changed to adhere to RFC 6241
* Changed commit lock error tag from "lock denied" to "in-use". * 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". * 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_CERT
* CLICON_SSL_SERVER_KEY * CLICON_SSL_SERVER_KEY
* CLICON_SSL_CA_CERT * 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): * 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 * Bodies in error retuns including html code have been removed
* Some (extra) CRLF:s have been removed * Some (extra) CRLF:s have been removed
@ -99,6 +128,8 @@ Expected: July 2020
### Corrected Bugs ### 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: 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: [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): * Fixed: [Need to add the possibility to use anchors around patterns #51](https://github.com/clicon/cligen/issues/51):

View file

@ -226,7 +226,7 @@ startup_extraxml(clicon_handle h,
/* Clear tmp db */ /* Clear tmp db */
if (xmldb_db_reset(h, tmp_db) < 0) if (xmldb_db_reset(h, tmp_db) < 0)
goto done; 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) if (clixon_plugin_reset_all(h, tmp_db) < 0)
goto done; goto done;
/* Extra XML can also be added via file */ /* Extra XML can also be added via file */
@ -238,13 +238,13 @@ startup_extraxml(clicon_handle h,
goto fail; 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 * It should be empty if extra-xml is null and reset plugins did nothing
* then skip validation. * then skip validation.
*/ */
if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0) if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0)
goto done; goto done;
if (xt0==NULL || xml_child_nr(xt0)==0) if (xmldb_empty_get(h, tmp_db))
goto ok; goto ok;
xt = NULL; xt = NULL;
/* Validate the tmp db and return possibly upgraded xml in xt /* Validate the tmp db and return possibly upgraded xml in xt

View file

@ -54,7 +54,8 @@
typedef struct { typedef struct {
uint32_t de_id; /* session id */ uint32_t de_id; /* session id */
cxobj *de_xml; /* cache */ 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; } db_elmnt;
/* /*

View file

@ -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_get(clicon_handle h, const char *db);
int xmldb_modified_set(clicon_handle h, const char *db, int value); 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); int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt);
#endif /* _CLIXON_DATASTORE_H */ #endif /* _CLIXON_DATASTORE_H */

View file

@ -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_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_default(cxobj *x); int xml_default(cxobj *x);
int xml_default_recurse(cxobj *xn); 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_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, void *arg); int xml_non_config_data(cxobj *xt, void *arg);

View file

@ -49,12 +49,13 @@
/* Struct containing module state differences between two modules or two /* Struct containing module state differences between two modules or two
* revisions of same module. * 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 { typedef struct {
int md_status; /* 0 if no module-state in a datastore, 1 if there is */ int md_status; /* 0 if no module-state in a datastore, 1 if there is */
char *md_set_id; /* server-specific identifier */ 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; } modstate_diff_t;
/* /*

View file

@ -636,12 +636,11 @@ clicon_argv_set(clicon_handle h,
return retval; 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] h Clicon handle
* @param[in] db Name of database * @param[in] db Name of database
* @retval de Database element * @retval de Database element
* @retval NULL None found * @retval NULL None found
* @note these use db_elmnt hash, not data
*/ */
db_elmnt * db_elmnt *
clicon_db_elmnt_get(clicon_handle h, clicon_db_elmnt_get(clicon_handle h,
@ -655,14 +654,12 @@ clicon_db_elmnt_get(clicon_handle h,
return NULL; 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] h Clicon handle
* @param[in] db Name of database * @param[in] db Name of database
* @param[in] de Database element * @param[in] de Database element
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* XXX add prefix to db to ensure uniqueness?
* @note these use db_elmnt hash, not data
*/ */
int int
clicon_db_elmnt_set(clicon_handle h, clicon_db_elmnt_set(clicon_handle h,

View file

@ -518,6 +518,26 @@ xmldb_modified_get(clicon_handle h,
return de->de_modified; 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 /*! Get modified flag from datastore
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] db Database name * @param[in] db Database name

View file

@ -72,6 +72,7 @@
#include "clixon_xpath.h" #include "clixon_xpath.h"
#include "clixon_json.h" #include "clixon_json.h"
#include "clixon_nacm.h" #include "clixon_nacm.h"
#include "clixon_path.h"
#include "clixon_netconf_lib.h" #include "clixon_netconf_lib.h"
#include "clixon_yang_module.h" #include "clixon_yang_module.h"
#include "clixon_xml_map.h" #include "clixon_xml_map.h"
@ -104,7 +105,7 @@ singleconfigroot(cxobj *xt,
} }
} }
if (i != 1){ 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; goto done;
} }
x = NULL; x = NULL;
@ -443,33 +444,63 @@ text_read_modstate(clicon_handle h,
return retval; 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 /*! Common read function that reads an XML tree from file
* @param[in] th Datastore text handle * @param[in] th Datastore text handle
* @param[in] db Symbolic database name, eg "candidate", "running" * @param[in] db Symbolic database name, eg "candidate", "running"
* @param[in] yb How to bind yang to XML top-level when parsing * @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Top-level yang spec * @param[in] yspec Top-level yang spec
* @param[out] xp XML tree read from file * @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 * @param[out] msdiff If set, return modules-state differences
* @retval -1 Error * @retval -1 Error
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set * @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval 1 OK * @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 int
xmldb_readfile(clicon_handle h, xmldb_readfile(clicon_handle h,
const char *db, const char *db,
yang_bind yb, yang_bind yb,
yang_stmt *yspec, yang_stmt *yspec,
cxobj **xp, cxobj **xp,
db_elmnt *de,
modstate_diff_t *msdiff) modstate_diff_t *msdiff)
{ {
int retval = -1; int retval = -1;
cxobj *x0 = NULL; cxobj *x0 = NULL;
char *dbfile = NULL; char *dbfile = NULL;
int fd = -1; int fd = -1;
char *format; char *format;
int ret; int ret;
if (xmldb_db2file(h, db, &dbfile) < 0) if (xmldb_db2file(h, db, &dbfile) < 0)
goto done; goto done;
@ -497,8 +528,9 @@ xmldb_readfile(clicon_handle h,
goto fail; goto fail;
#endif #endif
/* Always assert a top-level called "config". /* Always assert a top-level called "config".
To ensure that, deal with two cases: * To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */ * 1. File is empty <top/> -> rename top-level to "config"
*/
if (xml_child_nr(x0) == 0){ if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0) if (xml_name_set(x0, "config") < 0)
goto done; goto done;
@ -509,7 +541,10 @@ xmldb_readfile(clicon_handle h,
if (singleconfigroot(x0, &x0) < 0) if (singleconfigroot(x0, &x0) < 0)
goto done; 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. * which modules are used in the file.
*/ */
if (text_read_modstate(h, yspec, x0, msdiff) < 0) if (text_read_modstate(h, yspec, x0, msdiff) < 0)
@ -569,16 +604,36 @@ xmldb_get_nocache(clicon_handle h,
size_t xlen; size_t xlen;
int i; int i;
int ret; int ret;
db_elmnt de0 = {0,};
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; 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; goto done;
if (ret == 0) if (ret == 0)
goto fail; 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> */ /* Here xt looks like: <config>...</config> */
/* Given the xpath, return a vector of matches in xvec */ /* Given the xpath, return a vector of matches in xvec */
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
@ -664,6 +719,7 @@ xmldb_get_cache(clicon_handle h,
cxobj *x1t = NULL; cxobj *x1t = NULL;
db_elmnt de0 = {0,}; db_elmnt de0 = {0,};
int ret; int ret;
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); 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); de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */ 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 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; goto done;
if (ret == 0) if (ret == 0)
goto fail; 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? * No, argument against: we may want to have a semantically wrong file and wish to edit?
*/ */
de0.de_xml = x0t; de0.de_xml = x0t;
clicon_db_elmnt_set(h, db, &de0); clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
} /* x0t == NULL */ } /* x0t == NULL */
else else
x0t = de->de_xml; 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> */ /* Here x0t looks like: <config>...</config> */
/* Given the xpath, return a vector of matches in xvec /* Given the xpath, return a vector of matches in xvec
* Can we do everything in one go? * 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 * a) for every node that is found, copy to new tree
* b) if config dont dont state data * b) if config dont dont state data
*/ */
/* Here xt looks like: <config>...</config> */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done; goto done;
@ -703,6 +777,7 @@ xmldb_get_cache(clicon_handle h,
goto done; goto done;
xml_spec_set(x1t, xml_spec(x0t)); xml_spec_set(x1t, xml_spec(x0t));
if (xlen < 1000){ if (xlen < 1000){
/* This is optimized for the case when the tree is large and xlen is small /* 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. * 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 *de = NULL;
db_elmnt de0 = {0,}; db_elmnt de0 = {0,};
int ret; int ret;
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); 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); de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */ 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 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; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -806,6 +882,25 @@ xmldb_get_zerocopy(clicon_handle h,
} /* x0t == NULL */ } /* x0t == NULL */
else else
x0t = de->de_xml; 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> */ /* Here xt looks like: <config>...</config> */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done; goto done;
@ -874,7 +969,7 @@ xmldb_get(clicon_handle h,
* @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] copy Force copy. Overrides cache_zerocopy -> cache * @param[in] copy Force copy. Overrides cache_zerocopy -> cache
* @param[out] xret Single return XML tree. Free with xml_free() * @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 0 OK
* @retval -1 Error * @retval -1 Error
* @code * @code
@ -944,12 +1039,16 @@ xmldb_get0_clear(clicon_handle h,
if (x == NULL) if (x == NULL)
goto ok; 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) if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
goto done; goto done;
/* clear mark and change */ /* clear mark and change */
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(0xff)); (void*)(0xffff));
ok: ok:
retval = 0; retval = 0;
done: done:
@ -972,3 +1071,4 @@ xmldb_get0_free(clicon_handle h,
*xp = NULL; *xp = NULL;
return 0; return 0;
} }

View file

@ -39,6 +39,7 @@
/* /*
* Prototypes * 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 */ #endif /* _CLIXON_DATASTORE_READ_H */

View file

@ -481,9 +481,11 @@ text_modify(clicon_handle h,
switch(op){ switch(op){
case OP_CREATE: case OP_CREATE:
if (x0){ if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) if (xml_nopresence_default(x0) == 0){
goto done; if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto fail; goto done;
goto fail;
}
} }
case OP_REPLACE: /* fall thru */ case OP_REPLACE: /* fall thru */
case OP_MERGE: 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 there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){ if (x0 == NULL){
firsttime++; /* to avoid leakage on error, see fail from text_modify */ 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; goto done;
if (ret == 0) if (ret == 0)
goto fail; goto fail;
@ -987,10 +989,10 @@ xmldb_put(clicon_handle h,
db_elmnt de0 = {0,}; db_elmnt de0 = {0,};
if (de != NULL) if (de != NULL)
de0 = *de; de0 = *de;
if (de0.de_xml == NULL){ if (de0.de_xml == NULL)
de0.de_xml = x0; 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) if (xmldb_db2file(h, db, &dbfile) < 0)
goto done; goto done;
@ -1057,6 +1059,9 @@ xmldb_dump(clicon_handle h,
char *format; char *format;
int pretty; int pretty;
/* clear XML tree of defaults */
if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
goto done;
/* Add modstate first */ /* Add modstate first */
if ((x = clicon_modst_cache_get(h, 1)) != NULL){ if ((x = clicon_modst_cache_get(h, 1)) != NULL){
if ((xmodst = xml_dup(x)) == NULL) if ((xmodst = xml_dup(x)) == NULL)
@ -1079,3 +1084,4 @@ xmldb_dump(clicon_handle h,
done: done:
return retval; return retval;
} }

View file

@ -1100,7 +1100,9 @@ nacm_access(clicon_handle h,
/* Do initial nacm processing common to all access validation in /* Do initial nacm processing common to all access validation in
* RFC8341 3.4 */ * RFC8341 3.4 */
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol /* 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) if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
goto permit; goto permit;
enabled = xml_body(x); enabled = xml_body(x);
@ -1155,6 +1157,7 @@ nacm_access_pre(clicon_handle h,
cxobj *xnacm = NULL; cxobj *xnacm = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
/* Check clixon option: disabled, external tree or internal */
mode = clicon_option_str(h, "CLICON_NACM_MODE"); mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode == NULL) if (mode == NULL)
goto permit; goto permit;

View file

@ -1883,6 +1883,7 @@ xml_copy_one(cxobj *x0,
default: default:
break; break;
} }
xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT)); /* Maybe more flags */
retval = 0; retval = 0;
done: done:
return retval; return retval;

View file

@ -979,9 +979,223 @@ xml_namespace_change(cxobj *x,
return retval; return retval;
} }
/*! Add default values (if not set) int
* @param[in] xt XML tree with some node marked xml_default_create1(yang_stmt *y,
* Typically called in a recursive apply function: 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 0 OK
* @retval -1 Error * @retval -1 Error
*/ */
@ -990,79 +1204,22 @@ xml_default(cxobj *xt)
{ {
int retval = -1; int retval = -1;
yang_stmt *ys; 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){ if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0; retval = 0;
goto done; goto done;
} }
/* Check leaf defaults */ if (xml_default1(ys, xt) < 0)
if (yang_keyword_get(ys) == Y_CONTAINER || yang_keyword_get(ys) == Y_LIST || goto done;
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;
}
}
}
}
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
/*! Recursively fill in default values in a tree /*! Recursively fill in default values in an XML tree
* Alt to use xml_apply * @param[in] xt XML tree
* @retval 0 OK
* @retval -1 Error
*/ */
int int
xml_default_recurse(cxobj *xn) xml_default_recurse(cxobj *xn)
@ -1082,6 +1239,85 @@ xml_default_recurse(cxobj *xn)
return retval; 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 /*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree * @param[in] xt XML top of tree
*/ */

View file

@ -1029,6 +1029,7 @@ xml_insert2(cxobj *xp,
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort() * @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 int
xml_insert(cxobj *xp, xml_insert(cxobj *xp,

View file

@ -2,7 +2,9 @@
# Authentication and authorization and IETF NACM # Authentication and authorization and IETF NACM
# See RFC 8341 A.2 # See RFC 8341 A.2
# But replaced ietf-netconf-monitoring with * # 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) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi 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_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF
@ -140,7 +143,7 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/da
# explicitly disable nacm (regression on netgate bug) # explicitly disable nacm (regression on netgate bug)
new "disable nacm" 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" 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>]]>]]>$" expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"

View file

@ -123,6 +123,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE> <CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <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> <CLICON_NACM_CREDENTIALS>$mode</CLICON_NACM_CREDENTIALS>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -62,6 +62,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -42,6 +42,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -34,6 +34,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -30,6 +30,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <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> <CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config> </clixon-config>
EOF EOF
@ -62,7 +63,6 @@ EOF
# 6: expected return value of test2 # 6: expected return value of test2
# 7: expected return value of test3 # 7: expected return value of test3
# 8: startup mode: startup or init # 8: startup mode: startup or init
# 9: Dont set default values (nullify them)
testrun(){ testrun(){
enablenacm=$1 enablenacm=$1
readdefault=$2 readdefault=$2
@ -72,24 +72,9 @@ testrun(){
ret2=$6 ret2=$6
ret3=$7 ret3=$7
db=$8 db=$8
nulldef=$9
# Set default values (or not) NACM=$(cat <<EOF
if [ $nulldef -ne 0 ]; then <nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
# 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">
<enable-nacm>${enablenacm}</enable-nacm> <enable-nacm>${enablenacm}</enable-nacm>
<read-default>${readdefault}</read-default> <read-default>${readdefault}</read-default>
<write-default>${writedefault}</write-default> <write-default>${writedefault}</write-default>
@ -98,7 +83,6 @@ EOF
</nacm> </nacm>
EOF EOF
) )
fi
# Initial data # Initial data
XML='<x xmlns="urn:example:nacm">42</x>' XML='<x xmlns="urn:example:nacm">42</x>'
@ -117,6 +101,7 @@ EOF
start_backend -s $db -f $cfg start_backend -s $db -f $cfg
else else
new "Restart backend as eg follows: -Ff $cfg -s $db" new "Restart backend as eg follows: -Ff $cfg -s $db"
sleep 2
fi fi
new "waiting" new "waiting"
@ -132,12 +117,16 @@ EOF
wait_restconf wait_restconf
# Use POST (instead of startup) # Use POST (instead of startup)
# Note this only works because CLICON_NACM_DISABLED_ON_EMPTY is true
if [ $db = init ]; then if [ $db = init ]; then
new "Set Initial data using POST" # Must set NACM first
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 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 fi
#----------- First get #----------- First get
@ -204,39 +193,36 @@ EOF
# Run a lot of tests with different settings of default read/write/exec # 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 # Outer loop either starts from startup or inits config via restconf POST
for db in startup init; do for db in startup init; do
new "nacm enabled and all defaults permit" new "1. nacm enabled and all defaults permit"
testrun true permit permit permit 0 0 0 $db 0 testrun true permit permit permit 0 0 0 $db
new "nacm disabled and all defaults permit" new "2. nacm disabled and all defaults permit"
testrun false permit permit permit 0 0 0 $db 0 testrun false permit permit permit 0 0 0 $db
new "nacm disabled and all defaults deny" new "3. nacm disabled and all defaults deny"
testrun false deny deny deny 0 0 0 $db 0 testrun false deny deny deny 0 0 0 $db
new "nacm enabled, all defaults deny (expect fail)" new "4. nacm enabled, all defaults deny (expect fail)"
testrun true deny deny deny 1 1 1 $db 0 testrun true deny deny deny 1 1 1 $db
new "nacm enabled, exec default deny - read permit (expect fail)" new "5. nacm enabled, exec default deny - read permit (expect fail)"
testrun true permit deny deny 1 1 1 $db 0 testrun true permit deny deny 1 1 1 $db
new "nacm enabled, exec default deny - write permit (expect fail)" new "6. nacm enabled, exec default deny - write permit (expect fail)"
testrun true deny permit deny 1 1 1 $db 0 testrun true deny permit deny 1 1 1 $db
new "nacm enabled, exec default deny read/write permit (expect fail)" new "7. nacm enabled, exec default deny read/write permit (expect fail)"
testrun true permit permit deny 1 1 1 $db 0 testrun true permit permit deny 1 1 1 $db
new "nacm enabled, exec default permit, all others deny (expect fail)" new "8. nacm enabled, exec default permit, all others deny (expect fail)"
testrun true deny deny permit 2 1 2 $db 0 testrun true deny deny permit 2 1 2 $db
new "nacm enabled, exec default permit, read permit (expect fail)" new "9. nacm enabled, exec default permit, read permit (expect fail)"
testrun true permit deny permit 0 1 3 $db 0 # This is yang default 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)" new "10. nacm enabled, exec default permit, write permit (expect fail)"
# note last 1 means nullify all default values) testrun true deny permit permit 2 0 2 $db
testrun true xxx xxx xxx 0 1 3 init 1
new "nacm enabled, exec default permit, write permit (expect fail)"
testrun true deny permit permit 2 0 2 $db 0
done done
rm -rf $dir rm -rf $dir

View file

@ -48,7 +48,8 @@ module nacm-example{
} }
prefix nacm; prefix nacm;
container authentication { 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"; users";
leaf basic_auth{ leaf basic_auth{
description "Basic user / password authentication as in HTTP basic auth"; description "Basic user / password authentication as in HTTP basic auth";

View file

@ -35,6 +35,7 @@ cat <<EOF > $cfg
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS> <CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -48,6 +48,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -52,6 +52,7 @@ cat <<EOF > $cfg
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY> <CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS> <CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -27,9 +27,6 @@ module scaling{
yang-version 1.1; yang-version 1.1;
namespace "urn:example:clixon"; namespace "urn:example:clixon";
prefix ex; prefix ex;
import "clixon-config" {
prefix cc;
}
container x { container x {
list y { list y {
key "a"; key "a";

View file

@ -26,6 +26,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE> <CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config> </clixon-config>
EOF EOF

View file

@ -40,7 +40,6 @@ cat <<EOF > $cfg
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE> <CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT> <CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config> </clixon-config>
EOF EOF
# Create running-db containin the interface "run" OK # Create running-db containin the interface "run" OK

View file

@ -40,6 +40,11 @@ module interfaces{
reference reference
"RFC 2863: The Interfaces Group MIB"; "RFC 2863: The Interfaces Group MIB";
} }
leaf foo{
description "Should not appear";
type string;
default "bar";
}
container interfaces { container interfaces {
description description
"Interface configuration parameters."; "Interface configuration parameters.";
@ -52,8 +57,11 @@ module interfaces{
leaf description { leaf description {
type string; type string;
} }
leaf foo{
description "Should not appear";
type string;
default "bar";
}
leaf type { leaf type {
type string; type string;
mandatory true; mandatory true;
@ -124,6 +132,11 @@ module interfaces{
reference reference
"RFC 2863: The Interfaces Group MIB"; "RFC 2863: The Interfaces Group MIB";
} }
leaf foo{
description "Should not appear";
type string;
default "fie";
}
container interfaces { container interfaces {
description description
"Interface configuration parameters."; "Interface configuration parameters.";
@ -133,6 +146,11 @@ module interfaces{
leaf name { leaf name {
type string; type string;
} }
leaf foo{
description "Should not appear";
type string;
default "bar";
}
container docs{ container docs{
description "Original description is wrapped and renamed"; description "Original description is wrapped and renamed";
leaf descr { leaf descr {
@ -290,7 +308,6 @@ XML='<interfaces xmlns="urn:example:interfaces"><interface><name>e0</name><type>
ALL="<config>$MODSTATE$XML</config>" ALL="<config>$MODSTATE$XML</config>"
# -u means trigger example upgrade # -u means trigger example upgrade
new "test params: -s startup -f $cfg -- -u"
# kill old backend (if any) # kill old backend (if any)
new "kill old backend" new "kill old backend"
@ -301,6 +318,7 @@ fi
new "start backend -s startup -f $cfg -q -- -u" new "start backend -s startup -f $cfg -q -- -u"
output=$(sudo $clixon_backend -F -D $DBG -s startup -f $cfg -q -- -u) output=$(sudo $clixon_backend -F -D $DBG -s startup -f $cfg -q -- -u)
#echo "$output" #echo "$output"
if [ "$ALL" != "$output" ]; then if [ "$ALL" != "$output" ]; then
err "$ALL" "$output" err "$ALL" "$output"
fi fi

View file

@ -46,9 +46,8 @@ module clixon-config {
description description
"Added: CLICON_CLI_LINES_DEFAULT "Added: CLICON_CLI_LINES_DEFAULT
Added enum HIDE to CLICON_CLI_GENMODEL Added enum HIDE to CLICON_CLI_GENMODEL
Added CLICON_SSL_SERVER_CERT Added CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
Added CLICON_SSL_SERVER_KEY Added CLICON_NACM_DISABLED_ON_EMPTY";
Added CLICON_SSL_CA_CERT";
} }
revision 2020-04-23 { revision 2020-04-23 {
description description
@ -719,6 +718,20 @@ module clixon-config {
exact for example, this user must exist and be used, otherwise exact for example, this user must exist and be used, otherwise
another user (such as root or www) can pose as it."; 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 { leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean; type boolean;
default true; default true;