* NACM extension (RFC8341)

* NACM module support (RFC8341 A1+A2)
   * Recovery user "_nacm_recovery" added.
     * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
   * Example user changed adm1 to andy to comply with RFC8341 example

 * Yang code upgrade (RFC7950)
   * RPC method input parameters validated
     * see https://github.com/clicon/clixon/issues/4
* Correct XML namespace handling
   * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration:
```
      <rpc><my-own-method></rpc> # Wrong but accepted
      <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # Correct
        <my-own-method xmlns="http://example.net/me/my-own/1.0">
      </rpc>
```
   * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default)
   * XML to JSON translator support for mapping xmlns attribute to module name prefix.
   * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0"
   * See https://github.com/clicon/clixon/issues/49
* Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3)
* debug rpc added in example application (should be in clixon-config).
This commit is contained in:
Olof hagsand 2018-12-16 19:46:26 +01:00
parent e5c0b06cf9
commit ae1af8da9e
63 changed files with 1852 additions and 3492 deletions

3
.gitignore vendored
View file

@ -44,3 +44,6 @@ lib/clixon/clixon.h
build-root/*.tar.xz
build-root/*.rpm
build-root/rpmbuild
test/public
doc/html

View file

@ -8,21 +8,40 @@
### Major New features
* NACM extension (RFC8341)
* Move NACM files from backend to lib src dir
* NACM module support (RFC8341 A1+A2)
* Recovery user "_nacm_recovery" added.
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
* Example user changed adm1 to andy to comply with RFC8341 example
* Yang code upgrade (RFC7950)
* YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48)
* See https://github.com/clicon/clixon/issues/84
* RPC method input parameters validated
* see https://github.com/clicon/clixon/issues/47
* Support of submodule, include and belongs-to.
* Openconfig yang specs parsed: https://github.com/openconfig/public
* Improved unknown handling
* Improved "unknown" handling
* Yang load file configure options changed
* `CLICON_YANG_DIR` is changed from a single directory to a path of directories
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
* CLICON_YANG_MAIN_FILE Provides a filename with a single module filename.
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
* Correct XML namespace handling
* XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration:
```
<rpc><my-own-method></rpc> # Wrong but accepted
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # Correct
<my-own-method xmlns="http://example.net/me/my-own/1.0">
</rpc>
```
* To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default)
* XML to JSON translator support for mapping xmlns attribute to module name prefix.
* Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0"
* See https://github.com/clicon/clixon/issues/49
### API changes on existing features (you may need to change your code)
* Yang parser is stricter (see above) which may break parsing of existing yang specs.
* XML namespace handling is corrected (see above)
* For backward compatibility set config option CLICON_XML_NS_ITERATE
* Yang parser functions have changed signatures. Please check the source if you call these functions.
* Add `<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files.
* Change all @datamodel:tree to @datamodel in all CLI specification files
@ -30,6 +49,8 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes
* Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3)
* Removed return value ymodp from yang parse functions (eg yang_parse()).
* New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set.
* XML parser conformance to W3 spec
@ -42,6 +63,7 @@
* getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58)
### Known issues
* debug rpc added in example application (should be in clixon-config).
## 3.8.0 (6 Nov 2018)

View file

@ -31,9 +31,9 @@ Background
==========
Clixon was implemented to provide an open-source generic configuration
tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that.
tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture.
Users of clixon currently include:
Users of Clixon currently include:
* [Netgate](https://www.netgate.com)
* [CloudMon360](http://cloudmon360.com)
* [Grideye](http://hagsand.se/grideye)
@ -98,16 +98,16 @@ XML
Clixon has its own implementation of XML and XPATH implementation.
The standards covered include:
- [XML](https://www.w3.org/TR/2008/REC-xml-20081126)
- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208)
- [XPATH](https://www.w3.org/TR/xpath-10)
- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126)
- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208)
- [XPATH 1.0](https://www.w3.org/TR/xpath-10)
Not supported:
- <!DOCTYPE
- !DOCTYPE (ie DTD)
Yang
====
YANG and XML is at the heart of Clixon. Yang modules are used as a
YANG and XML is the heart of Clixon. Yang modules are used as a
specification for handling XML configuration data. The YANG spec is
used to generate an interactive CLI, netconf and restconf clients. It
also manages an XML datastore.
@ -126,7 +126,7 @@ However, the following YANG syntax modules are not implemented:
Restrictions on Yang types are as follows:
- The range statement does not support multiple values (RFC7895 sec 9.2.4)
- Submodules cannot re-use a prefix in an import statement that is already used for another imported module in the module that the submodule belongs to.
- Submodules cannot re-use a prefix in an import statement that is already used for another imported module in the module that the submodule belongs to. (see https://github.com/clicon/clixon/issues/60)
Netconf
=======
@ -136,7 +136,14 @@ Clixon implements the following NETCONF proposals or standards:
- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
Clixon does not yet support the following netconf features:
The following RFC6241 capabilities/features are hardcoded in Clixon:
- :candidate (RFC6241 8.3)
- :validate (RFC6241 8.6)
- :startup (RFC6241 8.7)
- :xpath (RFC6241 8.9)
- :notification: (RFC5277)
Clixon does not support the following netconf features:
- :url capability
- copy-config source config
@ -166,7 +173,7 @@ Datastore
=========
The Clixon datastore is a stand-alone XML based datastore. The idea is
to be able to use different datastores backends with the same
API.
API. Currently only an XML plain text datastore is supported.
The datastore is primarily designed to be used by Clixon but can be used
separately.
@ -183,7 +190,7 @@ subsystem can be used.
Restconf however needs credentials. This is done by writing a credentials callback in a restconf plugin. See:
* [FAQ](doc/FAQ.md#how-do-i-write-an-authentication-callback).
* [Example](example/README.md) has an example how to do this with HTTP basic auth.
* I have done this for another project using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration)
* It has been done for other projects using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration)
The clients send the ID of the user using a "username" attribute with
the RPC calls to the backend. Note that the backend trusts the clients
@ -211,7 +218,7 @@ The functionality is as follows:
* Groups are supported
* Rule-lists are supported
* Rules are supported as follows
* module-name: Only '*' supported
* module-name: fully supported
* access-operations: only '*' and 'exec' supported
* rpc-name: fully supported (eg edit-config/get-config, etc)
* action: fully supported (permit/deny)

View file

@ -80,5 +80,5 @@ distclean: clean
for i in $(SUBDIRS); \
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
tags:
TAGS:
find $(srcdir) -name '*.[chyl]' -print | etags -

View file

@ -467,12 +467,18 @@ from_client_edit_config(clicon_handle h,
goto ok;
}
}
if ((xc = xpath_first(xn, "config")) == NULL){
if ((xc = xpath_first(xn, "config")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>config</bad-element>", NULL) < 0)
goto done;
goto ok;
}
else{
/* <config> yang spec may be set to anyxml by ingress yang check,...*/
if (xml_spec(xc) != NULL)
xml_spec_set(xc, NULL);
/* Populate XML with Yang spec (why not do this in parser?)
* Maybe validate xml here as in text_modify_top?
*/
if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0)
@ -952,13 +958,16 @@ from_client_msg(clicon_handle h,
cxobj *xt = NULL;
cxobj *x;
cxobj *xe;
char *name = NULL;
char *rpc = NULL;
char *module = NULL;
char *db;
cbuf *cbret = NULL; /* return message */
int pid;
int ret;
char *username;
char *nacm_mode;
yang_spec *yspec;
yang_stmt *ye;
yang_stmt *ymod;
clicon_debug(1, "%s", __FUNCTION__);
pid = ce->ce_pid;
@ -974,62 +983,81 @@ from_client_msg(clicon_handle h,
goto done;
goto reply;
}
/* Get yang spec */
yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */
if ((x = xpath_first(xt, "/rpc")) == NULL){
if (netconf_malformed_message(cbret, "rpc keyword expected")< 0)
goto done;
goto reply;
}
/* Populate incoming XML tree with yang */
if (xml_spec_populate_rpc(h, x, yspec) < 0)
goto done;
if ((ret = xml_yang_validate_rpc(x)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
goto done;
goto reply;
}
xe = NULL;
username = xml_find_value(x, "username");
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
name = xml_name(xe);
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
/* Make NACM access control if enabled as "internal"*/
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){
if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0)
rpc = xml_name(xe);
if ((ye = xml_spec(xe)) == NULL){
if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0)
goto done;
if (!ret)
goto reply;
goto reply;
}
if (strcmp(name, "get-config") == 0){
if ((ymod = ys_module(ye)) == NULL){
clicon_err(OE_XML, ENOENT, "rpc yang does not have module");
goto done;
}
module = ymod->ys_argument;
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
/* Make NACM access control if enabled as "internal"*/
if ((ret = nacm_access(h, rpc, module, username, cbret)) < 0)
goto done;
if (ret == 0)
goto reply;
if (strcmp(rpc, "get-config") == 0){
if (from_client_get_config(h, xe, cbret) <0)
goto done;
}
else if (strcmp(name, "edit-config") == 0){
else if (strcmp(rpc, "edit-config") == 0){
if (from_client_edit_config(h, xe, pid, cbret) <0)
goto done;
}
else if (strcmp(name, "copy-config") == 0){
else if (strcmp(rpc, "copy-config") == 0){
if (from_client_copy_config(h, xe, pid, cbret) <0)
goto done;
}
else if (strcmp(name, "delete-config") == 0){
else if (strcmp(rpc, "delete-config") == 0){
if (from_client_delete_config(h, xe, pid, cbret) <0)
goto done;
}
else if (strcmp(name, "lock") == 0){
else if (strcmp(rpc, "lock") == 0){
if (from_client_lock(h, xe, pid, cbret) < 0)
goto done;
}
else if (strcmp(name, "unlock") == 0){
else if (strcmp(rpc, "unlock") == 0){
if (from_client_unlock(h, xe, pid, cbret) < 0)
goto done;
}
else if (strcmp(name, "get") == 0){
else if (strcmp(rpc, "get") == 0){
if (from_client_get(h, xe, cbret) < 0)
goto done;
}
else if (strcmp(name, "close-session") == 0){
else if (strcmp(rpc, "close-session") == 0){
xmldb_unlock_all(h, pid);
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
}
else if (strcmp(name, "kill-session") == 0){
else if (strcmp(rpc, "kill-session") == 0){
if (from_client_kill_session(h, xe, cbret) < 0)
goto done;
}
else if (strcmp(name, "validate") == 0){
else if (strcmp(rpc, "validate") == 0){
if ((db = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>source</bad-element>", NULL) < 0)
goto done;
@ -1038,19 +1066,19 @@ from_client_msg(clicon_handle h,
if (from_client_validate(h, db, cbret) < 0)
goto done;
}
else if (strcmp(name, "commit") == 0){
else if (strcmp(rpc, "commit") == 0){
if (from_client_commit(h, pid, cbret) < 0)
goto done;
}
else if (strcmp(name, "discard-changes") == 0){
else if (strcmp(rpc, "discard-changes") == 0){
if (from_client_discard_changes(h, pid, cbret) < 0)
goto done;
}
else if (strcmp(name, "create-subscription") == 0){
else if (strcmp(rpc, "create-subscription") == 0){
if (from_client_create_subscription(h, xe, ce, cbret) < 0)
goto done;
}
else if (strcmp(name, "debug") == 0){
else if (strcmp(rpc, "debug") == 0){
if (from_client_debug(h, xe, cbret) < 0)
goto done;
}
@ -1104,7 +1132,7 @@ from_client_msg(clicon_handle h,
/* Sanity: log if clicon_err() is not called ! */
if (retval < 0 && clicon_errno < 0)
clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)",
__FUNCTION__, name?name:"");
__FUNCTION__, rpc?rpc:"");
// clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;// -1 here terminates backend
}

View file

@ -95,6 +95,10 @@ process_incoming_packet(clicon_handle h,
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
if ((cbret = cbuf_new()) == NULL){
clicon_err(LOG_ERR, errno, "cbuf_new");
goto done;
}
yspec = clicon_dbspec_yang(h);
if ((str0 = strdup(cbuf_get(cb))) == NULL){
clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno));
@ -103,19 +107,25 @@ process_incoming_packet(clicon_handle h,
str = str0;
/* Parse incoming XML message */
if (xml_parse_string(str, yspec, &xreq) < 0){
if ((cbret = cbuf_new()) == NULL){
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
goto done;
netconf_output(1, cbret, "rpc-error");
}
else
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
free(str0);
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
goto done;
netconf_output(1, cbret, "rpc-error");
goto done;
}
free(str0);
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL)
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
int ret;
isrpc++;
if ((ret = xml_yang_validate_rpc(xrpc)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
goto done;
netconf_output(1, cbret, "rpc-error");
goto done;
}
}
else
if (xpath_first(xreq, "//hello") != NULL)
;
@ -134,30 +144,27 @@ process_incoming_packet(clicon_handle h,
else{ /* there is a return message in xret */
cxobj *xa, *xa2;
assert(xret);
if ((cbret = cbuf_new()) != NULL){
if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL;
/* Copy message-id attribute from incoming to reply.
* RFC 6241:
* If additional attributes are present in an <rpc> element, a NETCONF
* peer MUST return them unmodified in the <rpc-reply> element. This
* includes any "xmlns" attributes.
*/
while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){
if ((xa2 = xml_dup(xa)) ==NULL)
goto done;
if (xml_addsub(xc, xa2) < 0)
goto done;
}
add_preamble(cbret);
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
add_postamble(cbret);
if (netconf_output(1, cbret, "rpc-reply") < 0){
cbuf_free(cbret);
if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL;
/* Copy message-id attribute from incoming to reply.
* RFC 6241:
* If additional attributes are present in an <rpc> element, a NETCONF
* peer MUST return them unmodified in the <rpc-reply> element. This
* includes any "xmlns" attributes.
*/
while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){
if ((xa2 = xml_dup(xa)) ==NULL)
goto done;
}
if (xml_addsub(xc, xa2) < 0)
goto done;
}
add_preamble(cbret);
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
add_postamble(cbret);
if (netconf_output(1, cbret, "rpc-reply") < 0){
cbuf_free(cbret);
goto done;
}
}
}

View file

@ -869,6 +869,7 @@ netconf_application_rpc(clicon_handle h,
int retval = -1;
yang_spec *yspec = NULL; /* application yspec */
yang_stmt *yrpc = NULL;
yang_stmt *ymod = NULL;
yang_stmt *yinput;
yang_stmt *youtput;
cxobj *xoutput;
@ -888,26 +889,33 @@ netconf_application_rpc(clicon_handle h,
goto done;
}
cbuf_reset(cb);
if (xml_namespace(xn) == NULL){
if (ys_module_by_xml(yspec, xn, &ymod) < 0)
goto done;
if (ymod == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>rpc</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"<error-info>Not recognized</error-info>"
"<error-info>Not recognized module</error-info>"
"</rpc-error></rpc-reply>", xml_name(xn));
goto ok;
}
cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn));
/* Find yang rpc statement, return yang rpc statement if found */
if (yang_abs_schema_nodeid(yspec, xml_spec(xn), cbuf_get(cb), Y_RPC, &yrpc) < 0)
goto done;
yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn));
if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){
int i;
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL)
break;
}
}
/* Check if found */
if (yrpc != NULL){
/* 1. Check xn arguments with input statement. */
if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0)
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xn, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
@ -933,7 +941,7 @@ netconf_application_rpc(clicon_handle h,
if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){
xoutput=xpath_first(*xret, "/");
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
@ -954,7 +962,6 @@ netconf_application_rpc(clicon_handle h,
return retval;
}
/*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions.
* Call plugin handler if tag not found. If not handled by any handler, return
* error.
@ -985,7 +992,6 @@ netconf_rpc_dispatch(clicon_handle h,
if (xml_value_set(xa, username) < 0)
goto done;
}
xe = NULL;
while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xe), "get-config") == 0){

View file

@ -257,7 +257,6 @@ api_data_get2(clicon_handle h,
else{
if (xpath_vec(xret, "%s", &xvec, &xlen, path) < 0)
goto done;
clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, (int)xlen);
if (use_xml){
for (i=0; i<xlen; i++){
x = xvec[i];
@ -269,7 +268,7 @@ api_data_get2(clicon_handle h,
if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
goto done;
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
// clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
ok:
@ -408,9 +407,10 @@ api_data_post(clicon_handle h,
yang_spec *yspec;
cxobj *xa;
cxobj *xret = NULL;
cxobj *xretcom = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xe;
cxobj *xe; /* dont free */
char *username;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
@ -488,13 +488,22 @@ api_data_post(clicon_handle h,
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
if (clicon_rpc_discard_changes(h) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, use_xml) < 0)
goto done;
goto ok;
@ -512,6 +521,8 @@ api_data_post(clicon_handle h,
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata)
@ -623,7 +634,8 @@ api_data_put(clicon_handle h,
cxobj *xa;
char *api_path;
cxobj *xret = NULL;
cxobj *xretcom = NULL;
cxobj *xretcom = NULL; /* return from commit */
cxobj *xretdis = NULL; /* return from discard-changes */
cxobj *xerr = NULL; /* malloced must be freed */
cxobj *xe;
char *username;
@ -734,13 +746,23 @@ api_data_put(clicon_handle h,
goto ok;
}
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
if (clicon_rpc_discard_changes(h) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, use_xml) < 0)
goto done;
goto ok;
@ -758,6 +780,8 @@ api_data_put(clicon_handle h,
xml_free(xerr);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
if (xdata)
@ -791,7 +815,7 @@ api_data_patch(clicon_handle h,
return 0;
}
/*! Generic REST DELETE method
/*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
@ -821,7 +845,8 @@ api_data_delete(clicon_handle h,
yang_spec *yspec;
enum operation_type op = OP_DELETE;
cxobj *xret = NULL;
cxobj *xretcom = NULL;
cxobj *xretcom = NULL; /* return from commmit */
cxobj *xretdis = NULL; /* return from discard */
cxobj *xerr = NULL;
char *username;
@ -836,7 +861,6 @@ api_data_delete(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done;
xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0)
goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
@ -864,13 +888,22 @@ api_data_delete(clicon_handle h,
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){
if (clicon_rpc_discard_changes(h) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xerr, pretty, use_xml) < 0)
goto done;
goto ok;
@ -887,6 +920,8 @@ api_data_delete(clicon_handle h,
xml_free(xret);
if (xretcom)
xml_free(xretcom);
if (xretdis)
xml_free(xretdis);
if (xtop)
xml_free(xtop);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
@ -912,6 +947,11 @@ api_data_delete(clicon_handle h,
* data-model-specific RPC operations supported by the server. The
* server MAY omit this resource if no data-model-specific RPC
* operations are advertised.
* From ietf-restconf.yang:
* In XML, the YANG module namespace identifies the module:
* <system-restart xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
* In JSON, the YANG module name identifies the module:
* { 'ietf-system:system-restart' : [null] }
*/
int
api_operations_get(clicon_handle h,
@ -926,9 +966,9 @@ api_operations_get(clicon_handle h,
{
int retval = -1;
yang_spec *yspec;
yang_stmt *ym;
yang_stmt *ymod; /* yang module */
yang_stmt *yc;
char *modname;
char *namespace;
cbuf *cbx = NULL;
cxobj *xt = NULL;
@ -937,18 +977,17 @@ api_operations_get(clicon_handle h,
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<operations>");
ym = NULL;
while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) {
modname = ym->ys_argument;
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
namespace = yang_find_mynamespace(ymod);
yc = NULL;
while ((yc = yn_each((yang_node*)ym, yc)) != NULL) {
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
if (yc->ys_keyword != Y_RPC)
continue;
cprintf(cbx, "<%s:%s />", modname, yc->ys_argument);
cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace);
}
}
cprintf(cbx, "</operations>");
clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx));
if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
@ -962,7 +1001,6 @@ api_operations_get(clicon_handle h,
if (xml2json_cbuf(cbx, xt, pretty) < 0)
goto done;
}
clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
@ -1095,18 +1133,9 @@ api_operations_post(clicon_handle h,
if (xml_value_set(xa, username) < 0)
goto done;
}
/* XXX: something strange for rpc user */
if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0)
goto done;
#if 0
{
cbuf *c = cbuf_new();
clicon_xml2cbuf(c, xtop, 0, 0);
clicon_debug(1, "%s xtop:%s", __FUNCTION__, cbuf_get(c));
cbuf_free(c);
}
#endif
if (data && strlen(data)){
/* Parse input data as json or xml into xml */
if (parse_xml){
@ -1150,7 +1179,8 @@ api_operations_post(clicon_handle h,
}
if (yinput){
xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */
if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yinput) < 0)
/* XXX yinput <-> h ?*/
if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xbot, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
@ -1215,7 +1245,7 @@ api_operations_post(clicon_handle h,
#endif
cbuf_reset(cbx);
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0)
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if (xml_apply(xoutput, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
@ -1235,9 +1265,6 @@ api_operations_post(clicon_handle h,
else
if (xml2json_cbuf(cbx, xoutput, pretty) < 0)
goto done;
#if 1
clicon_debug(1, "%s cbx:%s", __FUNCTION__, cbuf_get(cbx));
#endif
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
}

View file

@ -49,7 +49,6 @@ INSTALLFLAGS = @INSTALLFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
with_restconf = @with_restconf@
with_keyvalue = @with_keyvalue@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
@ -117,5 +116,5 @@ distclean: clean
for i in $(SUBDIRS); \
do (cd $$i; $(MAKE) $(MFLAGS) $@); done
tags:
TAGS:
find $(srcdir) -name '*.[chyl]' -print | etags -

View file

@ -2,8 +2,7 @@
The Clixon datastore is a stand-alone XML based datastore. The idea is
to be able to use different datastores backends with the same
API. There is currently a key-value plugin based on qdbm and a plain
text-file datastore.
API. There is currently only a plain text-file datastore.
The datastore is primarily designed to be used by Clixon but can be used
separately.
@ -38,7 +37,7 @@ int xmldb_create(clicon_handle h, char *db);
To use the API, a client needs the following:
- A clicon handle.
- A datastore plugin, such as a text.so or keyvalue.so. These are normally built and installed at Clixon make.
- A datastore plugin, such as a text.so. These are normally built and installed at Clixon make.
- A directory where to store databases
- A yang specification. This needs to be parsed using the Clixon yang_parse() method.

View file

@ -31,14 +31,6 @@
***** END LICENSE BLOCK *****
* Examples:
./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get /
sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 '<config>eth66</config>'
sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / '<config><interfaces><interface><name>eth0</name><enabled>true</enabled></interface></interfaces></config>'
*/
#ifdef HAVE_CONFIG_H
@ -247,6 +239,7 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0);
}
_CLICON_XML_NS_ITERATE = 1;
if (xml_parse_string(argv[2], NULL, &xt) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)

View file

@ -1,98 +0,0 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Alternatively, the contents of this file may be used under the terms of
# the GNU General Public License Version 3 or later (the "GPL"),
# in which case the provisions of the GPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of the GPL, and not to allow others to
# use your version of this file under the terms of Apache License version 2,
# indicate your decision by deleting the provisions above and replace them with
# the notice and other provisions required by the GPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the Apache License version 2 or the GPL.
#
# ***** END LICENSE BLOCK *****
#
VPATH = @srcdir@
prefix = @prefix@
datarootdir = @datarootdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
exec_prefix = @exec_prefix@
bindir = @bindir@
libdir = @libdir@
dbdir = @prefix@/db
mandir = @mandir@
libexecdir = @libexecdir@
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
VPATH = @srcdir@
CC = @CC@
CFLAGS = @CFLAGS@ -rdynamic -fPIC
INSTALLFLAGS = @INSTALLFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
DATASTORE = keyvalue
CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
PLUGIN = $(DATASTORE).so
SRC = clixon_keyvalue.c clixon_qdb.c clixon_chunk.c
OBJS = $(SRC:.c=.o)
all: $(PLUGIN)
$(PLUGIN): $(SRC)
$(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS)
clean:
rm -f $(PLUGIN) $(OBJS) *.core
distclean: clean
rm -f Makefile *~ .depend
.SUFFIXES:
.SUFFIXES: .c .o
.c.o: $(SRC)
$(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $<
install: $(PLUGIN)
install -d -m 0755 $(DESTDIR)$(libdir)/xmldb
install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb
install-include:
uninstall:
rm -rf $(DESTDIR)$(libdir)/xmldb/$(PLUGIN)
TAGS:
find . -name '*.[chyl]' -print | etags -
depend:
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend
#include .depend

View file

@ -1,794 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2, indicate
your decision by deleting the provisions above and replace them with the
notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
/* Error handling: dont use clicon_err, treat as unix system calls. That is,
ensure errno is set and return -1/NULL */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/types.h>
/* clicon */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_chunk.h"
/*
* The chunk head array for the predefined chunk sizes.
*/
static chunk_head_t chunk_heads[CHUNK_HEADS];
/*
* Did we initialize the chunk heads yet?
*/
static int chunk_initialized = 0;
/*
* The pagesize of this system
*/
static int chunk_pagesz;
/*
* List of chunk groups
*/
static chunk_group_t *chunk_grp;
/*
* Hack to tell unchunk() not to remove chunk_group if empty
*/
static int dont_unchunk_group;
/*
* Initialize chunk library
*/
static void
chunk_initialize ()
{
int pgs;
register int idx;
chunk_pagesz = getpagesize();
bzero (&chunk_heads, sizeof(chunk_heads));
for (idx = 0; idx < CHUNK_HEADS; idx++) {
chunk_head_t *chead = &chunk_heads[idx];
/*
* Calculate the size of a block
*/
pgs = (0x01lu << (CHUNK_BASE + idx)) / chunk_pagesz;
if (pgs == 0)
pgs = 1;
chead->ch_blksz = pgs * chunk_pagesz;
/*
* Chunks per block is 1 for all size above a page. For sizes
* (including the chunk header) less than a page it's as many
* as fits
*/
chead->ch_nchkperblk = chead->ch_blksz / (0x01lu << (CHUNK_BASE + idx));
/*
* Size of each chunk is:
* (size + chnkhdr) * ncnkperblk = blksiz - blkhdr
*/
chead->ch_size =
(chead->ch_blksz / chead->ch_nchkperblk)
- sizeof(chunk_t);
}
/* Zero misc variables */
chunk_grp = NULL;
dont_unchunk_group = 0;
chunk_initialized = 1;
}
/*
* chunk_new_block() - Allocate new block, initialize it and it's chunks.
*/
static int
chunk_new_block (chunk_head_t *chead)
{
register int idx;
register char *c;
chunk_block_t *blk;
chunk_t *cnk;
/* Map block header mem */
blk = (chunk_block_t *)
mmap(NULL, sizeof(chunk_block_t),
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (blk == MAP_FAILED)
return -1;
memset((void *)blk, 0, sizeof(*blk));
/* Allocate chunk block */
blk->cb_blk = (void *)
mmap(NULL, chead->ch_blksz,
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (blk->cb_blk == MAP_FAILED) {
munmap(blk, chead->ch_blksz);
return -1;
}
memset(blk->cb_blk, 0, chead->ch_blksz);
/* Initialize chunk header */
blk->cb_head = chead;
INSQ(blk, chead->ch_blks);
chead->ch_nblks++;
/* Initialize chunks */
c = ((char *)blk->cb_blk);
for (idx = 0; idx < chead->ch_nchkperblk; idx++) {
cnk = (chunk_t *)c;
cnk->c_blk = blk;
INSQ(cnk, chead->ch_free);
chead->ch_nfree++;
c += (chead->ch_size + sizeof(chunk_t));
}
return 0;
}
/*
* chunk_release_block() - Unqueue a block, it's chunks and free mem
*/
static void
chunk_release_block(chunk_block_t *cblk)
{
int idx;
char *c;
chunk_t *cnk;
chunk_head_t *chead;
chead = cblk->cb_head;
/*
* Dequeue block
*/
DELQ(cblk, chead->ch_blks, chunk_block_t *);
chead->ch_nblks--;
/*
* Dequeue all chunks in the block
*/
c = (char *)cblk->cb_blk;
for (idx = 0; idx < chead->ch_nchkperblk; idx++) {
cnk = (chunk_t *)c;
DELQ(cnk, chead->ch_free, chunk_t *);
chead->ch_nfree--;
c += (chead->ch_size + sizeof(chunk_t));
}
/*
* Free block
*/
munmap((void *)cblk->cb_blk, chead->ch_blksz);
munmap((void *)cblk, sizeof(*cblk));
}
/*
* chunk_alloc() - Map new chunk of memory
*/
static void *
chunk_alloc(size_t len)
{
register int idx;
chunk_head_t *chead;
chunk_t *cnk;
if (!len)
return (void *)NULL;
/* Find sufficient sized block head */
for (idx = 0; idx < CHUNK_HEADS; idx++)
if (chunk_heads[idx].ch_size >= len)
break;
/* Too large chunk? */
if (idx >= CHUNK_HEADS) {
errno = ENOMEM;
return (void *)NULL;
}
chead = &chunk_heads[idx];
/* Get new block if necessary */
if (!chead->ch_nfree)
if (chunk_new_block(chead))
return (void *)NULL;
/* Move a free chunk to the in-use list */
cnk = chead->ch_free;
DELQ(cnk, chead->ch_free, chunk_t *);
chead->ch_nfree--;
INSQ(cnk, chead->ch_cnks);
/* Add reference to the corresponding block */
cnk->c_blk->cb_ref++;
#ifdef CHUNK_DIAG
/* Clear diag info */
bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag));
#endif /* CHUNK_DIAG */
/* Return pointer to first byte after the chunk header */
return (void *) (cnk + 1);
}
/*
* chunk() - Map new chunk of memory in group
*/
void *
#ifdef CHUNK_DIAG
_chunk(size_t len, const char *name, const char *file, int line)
#else
chunk(size_t len, const char *name)
#endif
{
int newgrp = 0;
void *ptr = NULL;
chunk_t *cnk;
chunk_group_t *tmp;
chunk_group_t *grp = NULL;
chunk_grpent_t *ent = NULL;
/* Make sure chunk_heads are initialized */
if (!chunk_initialized)
chunk_initialize();
if (!len)
return (void *)NULL;
/* Get actual chunk
*/
ptr = chunk_alloc(len);
if (!ptr)
goto error;
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
#ifdef CHUNK_DIAG
/* Store who reuested us
*/
cnk->c_diag.cd_file = file;
cnk->c_diag.cd_line = line;
#endif /* CHUNK_DIAG */
/* No name given. Get an ungrouped chunk
*/
if (!name)
return ptr;
/* Try to find already existing entry
*/
if (chunk_grp) {
tmp = chunk_grp;
do {
if (!strcmp(tmp->cg_name, name)) {
grp = tmp;
break;
}
tmp = NEXTQ(chunk_group_t *, tmp);
} while (tmp != chunk_grp);
}
/* New group.
*/
if ( !grp ) {
grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t));
if (!grp)
goto error;
bzero(grp, sizeof(chunk_group_t));
grp->cg_name = (char *) chunk_alloc(strlen(name) + 1);
if (!grp->cg_name)
goto error;
bcopy(name, grp->cg_name, strlen(name)+1);
newgrp = 1;
}
/* Get new entry.
*/
ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t));
if (!ent)
goto error;
bzero(ent, sizeof(chunk_grpent_t));
/* Now put everything together
*/
cnk->c_grpent = ent;
ent->ce_cnk = cnk;
ent->ce_grp = grp;
INSQ(ent, grp->cg_ent);
if (newgrp)
INSQ(grp, chunk_grp);
return (ptr);
error:
if (grp && newgrp) {
if (grp->cg_name)
unchunk(grp->cg_name);
unchunk(grp);
}
if (ent)
unchunk(ent);
if (ptr)
unchunk(ptr);
return (void *) NULL;
}
/*
* rechunk() - Resize previously allocated chunk.
*/
void *
#ifdef CHUNK_DIAG
_rechunk(void *ptr, size_t len, const char *name, const char *file, int line)
#else
rechunk(void *ptr, size_t len, const char *name)
#endif
{
int idx;
void *new;
chunk_t *cnk;
chunk_t *newcnk;
chunk_head_t *chead;
chunk_head_t *newchead;
/* No previoud chunk, get new
*/
if (!ptr) {
#ifdef CHUNK_DIAG
return _chunk(len, name, file, line);
#else
return chunk(len, name);
#endif
}
/* Zero length, free chunk
*/
if (len == 0) {
unchunk(ptr);
return (void *) NULL;
}
/* Rewind pointer to beginning of chunk header
*/
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
chead = cnk->c_blk->cb_head;
/* Find sufficient sized block head
*/
for (idx = 0; idx < CHUNK_HEADS; idx++)
if (chunk_heads[idx].ch_size >= len)
break;
/* Too large chunk? */
if (idx >= CHUNK_HEADS) {
errno = ENOMEM;
return (void *)NULL;
}
/* Check if chunk size remains unchanged
*/
if (chunk_heads[idx].ch_size == chead->ch_size)
return (ptr);
/* Get new chunk
*/
#ifdef CHUNK_DIAG
new = _chunk(len, name, file, line);
#else
new = chunk(len, name);
#endif
if (!new)
return (void *) NULL;
newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t));
newchead = newcnk->c_blk->cb_head;
/* Copy contents to new chunk
*/
bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size));
/* Free old chunk
*/
unchunk(ptr);
return (new);
}
/*
* unchunk() - Release chunk
*/
void
unchunk(void *ptr)
{
chunk_t *cnk;
chunk_head_t *chead;
chunk_block_t *cblk;
chunk_grpent_t *ent;
chunk_group_t *grp;
if (!chunk_initialized)
return;
/* Rewind pointer to beginning of chunk header
*/
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
cblk = cnk->c_blk;
chead = cblk->cb_head;
/* Move chunk back to free list
*/
DELQ(cnk, chead->ch_cnks, chunk_t *);
INSQ(cnk, chead->ch_free);
chead->ch_nfree++;
/* If chunk is grouped, remove from group.
*/
ent = cnk->c_grpent;
if (ent) {
grp = ent->ce_grp;
DELQ(ent, grp->cg_ent, chunk_grpent_t *);
unchunk(ent);
cnk->c_grpent = NULL;
/* Group empty? */
if (!dont_unchunk_group && !grp->cg_ent) {
DELQ(grp, chunk_grp, chunk_group_t *);
unchunk(grp->cg_name);
unchunk(grp);
}
}
/* Check block refs is nil, if so free it
*/
cblk->cb_ref--;
if (cblk->cb_ref == 0)
chunk_release_block (cblk);
}
/*
* unchunk_group() - Release all group chunks.
*/
void
unchunk_group(const char *name)
{
chunk_group_t *tmp;
chunk_group_t *grp = NULL;
chunk_t *cnk;
if (!chunk_initialized)
return;
/* Try to find already existing entry
*/
if (chunk_grp) {
tmp = chunk_grp;
do {
if (!strcmp(tmp->cg_name, name)) {
grp = tmp;
break;
}
tmp = NEXTQ(chunk_group_t *, tmp);
} while (tmp != chunk_grp);
}
if (!grp)
return;
/* Walk through all chunks in group an free them
*/
dont_unchunk_group = 1;
while (grp->cg_ent) {
cnk = grp->cg_ent->ce_cnk;
unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t)));
}
dont_unchunk_group = 0;
/* Remove group from list and free it
*/
DELQ(grp, chunk_grp, chunk_group_t *);
unchunk(grp->cg_name);
unchunk(grp);
}
/*
* chunkdup() - Copy block of data to a new chunk of memory in group
*/
void *
#ifdef CHUNK_DIAG
_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line)
#else
chunkdup(const void *ptr, size_t len, const char *name)
#endif
{
void *new;
/* No input data or no length
*/
if (!ptr || len <= 0)
return (void *)NULL;
/* Get new chunk
*/
#ifdef CHUNK_DIAG
new = _chunk(len, name, file, line);
#else
new = chunk(len, name);
#endif
if (!new)
return (void *)NULL;
/* Copy data to new chunk
*/
memcpy(new, ptr, len);
return (new);
}
/*
* chunksize() - Return size of memory chunk.
*/
size_t
chunksize(void *ptr)
{
chunk_t *cnk;
chunk_head_t *chead;
chunk_block_t *cblk;
if (!chunk_initialized)
return -1;
/* Rewind pointer to beginning of chunk header
*/
cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t));
cblk = cnk->c_blk;
chead = cblk->cb_head;
return chead->ch_size;
}
/*
* chunk_strncat() - Concatenate 'n' characters to a chunk allocated string. If
* 'n' is zero, do the whole src string.
*
*/
char *
#ifdef CHUNK_DIAG
_chunk_strncat(const char *dst, const char *src, size_t n, const char *name,
const char *file, int line)
#else
chunk_strncat(const char *dst, const char *src, size_t n, const char *name)
#endif
{
size_t len;
char *new;
void *ptr = (void *)dst;
if (n == 0) /* zero length means cat whole string */
n = strlen(src);
len = (dst ? strlen(dst) : 0) + n + 1;
#ifdef CHUNK_DIAG
ptr = _rechunk(ptr, len, name, file, line);
#else
ptr = rechunk(ptr, len, name);
#endif
if (ptr == NULL)
return NULL;
new = (char *)ptr;
new += strlen(new);
while (n-- > 0 && *src)
*new++ = *src++;
*new = '\0';
return (char *)ptr;
}
/*
* chunk_sprintf() - Format string into new chunk.
*/
char *
#ifdef CHUNK_DIAG
_chunk_sprintf(const char *name, const char *file,
int line, const char *fmt, ...)
#else
chunk_sprintf(const char *name, char *fmt, ...)
#endif
{
size_t len;
char *str;
va_list args;
/* Calculate formatted string length */
va_start(args, fmt);
len = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
/* get chunk */
#ifdef CHUNK_DIAG
str = _chunk(len, name, file, line);
#else
str = chunk(len, name);
#endif
if (str == NULL)
return NULL;
/* Format string */
va_start(args, fmt);
len = vsnprintf(str, len, fmt, args);
va_end(args);
return str;
}
#ifdef CHUNK_DIAG
/*
* chunk_check() - Report all non-freed chunk for given group (if any)
*/
void
chunk_check(FILE *fout, const char *name)
{
int idx;
chunk_t *cnk;
chunk_group_t *tmp;
chunk_group_t *grp = NULL;
chunk_grpent_t *ent;
if (!chunk_initialized)
return;
/* No name given, walk through everything
*/
if (name == (const char *)NULL) {
for (idx = 0; idx < CHUNK_HEADS; idx++) {
chunk_head_t *chead = &chunk_heads[idx];
cnk = chead->ch_cnks;
if (cnk == (chunk_t *)NULL)
continue;
do {
/* If no file name it's an internal chunk */
if (cnk->c_diag.cd_file)
clicon_debug(0,
"%s:%d,\t%zu bytes (%p), group \"%s\"\n",
cnk->c_diag.cd_file,
cnk->c_diag.cd_line,
cnk->c_blk->cb_head->ch_size,
(cnk +1),
cnk->c_grpent ?
cnk->c_grpent->ce_grp->cg_name :
"NULL");
cnk = NEXTQ(chunk_t *, cnk);
} while (cnk != chead->ch_cnks);
}
}
/* Walk through group
*/
else {
/* Try to find already existing entry
*/
if (chunk_grp) {
tmp = chunk_grp;
do {
if (!strcmp(tmp->cg_name, name)) {
grp = tmp;
break;
}
tmp = NEXTQ(chunk_group_t *, tmp);
} while (tmp != chunk_grp);
}
if (!grp)
return;
ent = grp->cg_ent;
do {
cnk = ent->ce_cnk;
fprintf(fout ? fout : stdout,
"%s:%d,\t%zu bytes (%p), group \"%s\"\n",
cnk->c_diag.cd_file,
cnk->c_diag.cd_line,
cnk->c_blk->cb_head->ch_size,
(cnk +1),
cnk->c_grpent ?
cnk->c_grpent->ce_grp->cg_name :
"NULL");
ent = NEXTQ(chunk_grpent_t *, ent);
} while (ent != grp->cg_ent);
}
}
#else /* CHUNK_DIAG */
void
chunk_check(FILE *fout, const char *name)
{
}
#endif /* CHUNK_DIAG */

View file

@ -1,177 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
*/
#ifndef _CLIXON_CHUNK_H_
#define _CLIXON_CHUNK_H_
/*
* Compile with chunk diagnostics. XXX Should be in Makefile.in ??
*/
#undef CHUNK_DIAG
/*
* Base number of bits to shift getting the size of a chunk_head.
*/
#define CHUNK_BASE 6
/*
* Number of predefined chunk sizes. I.e. the number of chunk heads in the
* chunk_heads vector.
*/
#define CHUNK_HEADS (32 - CHUNK_BASE)
#ifdef CHUNK_DIAG
/*
* Chunk diagnostics
*/
typedef struct _chunk_diag_t {
const char *cd_file; /* File which requested chunk */
int cd_line; /* Line in requesting file */
} chunk_diag_t;
#endif /* CHUNK_DIAG */
/*
* The block header.
*/
struct _chunk_head_t;
typedef struct _chunk_head_t chunk_head_t;
typedef struct _chunk_block_t {
qelem_t cb_qelem; /* Circular queue of blocks */
chunk_head_t *cb_head; /* The chunk head I belong to */
void *cb_blk; /* Allocated memory block */
uint16_t cb_ref; /* Number of used chunks of block */
} chunk_block_t;
/*
* The chunk header.
*/
struct _chunk_grpent_t;
typedef struct _chunk_grpent_t chunk_grpent_t;
typedef struct _chunk_t {
qelem_t c_qelem; /* Circular queue of chunks */
chunk_block_t *c_blk; /* The block I belong to */
#ifdef CHUNK_DIAG
chunk_diag_t c_diag; /* The diagnostics structure */
#endif /* CHUNK_DIAG */
chunk_grpent_t *c_grpent;
} chunk_t;
/*
* The head of a chunk size. Each predefined size has it's own head keeping
* track of all blocks and chunks for the size.
*/
struct _chunk_head_t {
size_t ch_size; /* Chunk size */
int ch_nchkperblk; /* Number pf chunks per block */
size_t ch_blksz; /* Size of a block */
int ch_nblks; /* Number of allocated blocks */
chunk_block_t *ch_blks; /* Circular list of blocks */
chunk_t *ch_cnks; /* Circular list of chunks in use */
size_t ch_nfree; /* Number of free chunks */
chunk_t *ch_free; /* Circular list of free chunks */
};
/*
* The chunk group structure.
*/
typedef struct _chunk_group_t {
qelem_t cg_qelem; /* List of chunk groups */
char *cg_name; /* Name of group */
chunk_grpent_t *cg_ent; /* List of chunks in the group */
} chunk_group_t;
/*
* The chunk group entry structure.
*/
struct _chunk_grpent_t {
qelem_t ce_qelem; /* Circular list of entries */
chunk_group_t *ce_grp; /* The group I belong to */
chunk_t *ce_cnk; /* Pointer to the chunk */
};
/*
* Public function declarations
*/
#ifdef CHUNK_DIAG
void *_chunk (size_t, const char *, const char *, int);
#define chunk(siz,label) _chunk((siz),(label),__FILE__,__LINE__)
void *_rechunk (void *, size_t, const char *, const char *, int);
#define rechunk(ptr,siz,label) _rechunk((ptr),(siz),(label),__FILE__,__LINE__)
void *_chunkdup (const void *, size_t, const char *, const char *, int);
#define chunkdup(ptr,siz,label) _chunkdup((ptr),(siz),(label),__FILE__,__LINE__)
char *_chunk_strncat (const char *, const char *, size_t, const char *, const char *, int);
#define chunk_strncat(str,new,n,label) _chunk_strncat((str),(new),(n),(label),__FILE__,__LINE__)
char *_chunk_sprintf (const char *, const char *, int, const char *, ...);
#define chunk_sprintf(label,fmt,...) _chunk_sprintf((label),__FILE__,__LINE__,(fmt),__VA_ARGS__)
#else /* CHUNK_DIAG */
void *chunk (size_t, const char *);
void *rechunk (void *, size_t, const char *);
void *chunkdup (const void *, size_t, const char *);
char *chunk_strncat (const char *, const char *, size_t, const char *);
char *chunk_sprintf (const char *, char *, ...);
#endif /* CHUNK_DIAG */
void unchunk (void *);
void unchunk_group (const char *);
void chunk_check (FILE *, const char *);
size_t chunksize (void *);
#endif /* _CLIXON_CHUNK_H_ */

File diff suppressed because it is too large Load diff

View file

@ -1,54 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
Key-value store
*/
#ifndef _CLIXON_KEYVALUE_H
#define _CLIXON_KEYVALUE_H
/*
* Prototypes
*/
int kv_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop);
int kv_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt);
int kv_dump(FILE *f, char *dbfilename, char *rxkey);
int kv_copy(xmldb_handle h, const char *from, const char *to);
int kv_lock(xmldb_handle h, const char *db, int pid);
int kv_unlock(xmldb_handle h, const char *db);
int kv_unlock_all(xmldb_handle h, int pid);
int kv_islocked(xmldb_handle h, const char *db);
int kv_exists(xmldb_handle h, const char *db);
int kv_delete(xmldb_handle h, const char *db);
int kv_init(xmldb_handle h, const char *db);
#endif /* _CLIXON_KEYVALUE_H */

View file

@ -1,588 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* @note Some unclarities with locking. man dpopen defines the following flags
* with dpopen:
* `DP_ONOLCK', which means it opens a database file without
* file locking,
* `DP_OLCKNB', which means locking is performed without blocking.
*
* While connecting as a writer, an exclusive lock is invoked to
* the database file. While connecting as a reader, a shared lock is
* invoked to the database file. The thread blocks until the lock is
* achieved. If `DP_ONOLCK' is used, the application is responsible
* for exclusion control.
* The code below uses for
* write, delete: DP_OLCKNB
* read: DP_OLCKNB
* This means that a write fails if one or many reads are occurring, and
* a read or write fails if a write is occurring, and
* QDBM allows a single write _or_ multiple readers, but
* not both. This is obviously extremely limiting.
* NOTE, the locking in netconf and xmldb is a write lock.
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <limits.h>
#include <regex.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/param.h>
#ifdef HAVE_DEPOT_H
#include <depot.h> /* qdb api */
#else /* HAVE_QDBM_DEPOT_H */
#include <qdbm/depot.h> /* qdb api */
#endif
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "clixon_chunk.h"
#include "clixon_qdb.h"
/*! Initialize database
* @param[in] file database file
* @param[in] omode see man dpopen
*/
static int
db_init_mode(char *file,
int omode)
{
DEPOT *dp;
/* Open database for writing */
if ((dp = dpopen(file, omode | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "dpopen(%s): %s",
file,
dperrmsg(dpecode));
return -1;
}
clicon_debug(1, "db_init(%s)", file);
if (dpclose(dp) == 0){
clicon_err(OE_DB, errno, "db_set: dpclose: %s",
dperrmsg(dpecode));
return -1;
}
return 0;
}
/*! Open database for reading and writing
* @param[in] file database file
*/
int
db_init(char *file)
{
return db_init_mode(file, DP_OWRITER | DP_OCREAT ); /* DP_OTRUNC? */
}
/*! Remove database by removing file, if it exists *
* @param[in] file database file
*/
int
db_delete(char *file)
{
struct stat sb;
if (stat(file, &sb) < 0){
return 0;
}
if (unlink(file) < 0){
clicon_err(OE_DB, errno, "unlink %s", file);
return -1;
}
return 0;
}
/*! Write data to database
* @param[in] file database file
* @param[in] key database key
* @param[out] data Buffer containing content
* @param[out] datalen Length of buffer
* @retval 0 if OK: value returned. If not found, zero string returned
* @retval -1 on error
*/
int
db_set(char *file,
char *key,
void *data,
size_t datalen)
{
DEPOT *dp;
/* Open database for writing */
if ((dp = dpopen(file, DP_OWRITER|DP_OLCKNB , 0)) == NULL){
clicon_err(OE_DB, errno, "db_set: dpopen(%s): %s",
file,
dperrmsg(dpecode));
return -1;
}
clicon_debug(2, "%s: db_put(%s, len:%d)",
file, key, (int)datalen);
if (dpput(dp, key, -1, data, datalen, DP_DOVER) == 0){
clicon_err(OE_DB, errno, "%s: db_set: dpput(%s, %d): %s",
file,
key,
datalen,
dperrmsg(dpecode));
dpclose(dp);
return -1;
}
if (dpclose(dp) == 0){
clicon_err(OE_DB, 0, "db_set: dpclose: %s", dperrmsg(dpecode));
return -1;
}
return 0;
}
/*! Get data from database
* @param[in] file database file
* @param[in] key database key
* @param[out] data Pre-allocated buffer where data corresponding key is placed
* @param[out] datalen Length of pre-allocated buffer
* @retval 0 if OK: value returned. If not found, zero string returned
* @retval -1 on error
* @see db_get_alloc Allocates memory
*/
int
db_get(char *file,
char *key,
void *data,
size_t *datalen)
{
DEPOT *dp;
int len;
/* Open database for readinf */
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "%s: db_get(%s, %d): dpopen: %s",
file,
key,
datalen,
dperrmsg(dpecode));
return -1;
}
len = dpgetwb(dp, key, -1, 0, *datalen, data);
if (len < 0){
if (dpecode == DP_ENOITEM){
data = NULL;
*datalen = 0;
}
else{
clicon_err(OE_DB, errno, "db_get: dpgetwb: %s (%d)",
dperrmsg(dpecode), dpecode);
dpclose(dp);
return -1;
}
}
else
*datalen = len;
clicon_debug(2, "db_get(%s, %s)=%s", file, key, (char*)data);
if (dpclose(dp) == 0){
clicon_err(OE_DB, errno, "db_get: dpclose: %s", dperrmsg(dpecode));
return -1;
}
return 0;
}
/*! Get data from database and allocates memory
* Similar to db_get but returns a malloced pointer to the data instead
* of copying data to pre-allocated buffer. This is necessary if the
* length of the data is not known when calling the function.
* @param[in] file database file
* @param[in] key database key
* @param[out] data Allocated buffer where data corresponding key is placed
* @param[out] datalen Length of pre-allocated buffer
* @retval 0 if OK: value returned. If not found, zero string returned
* @retval -1 on error
* @note: *data needs to be freed after use.
* @code
* char *lvec = NULL;
* size_t len = 0;
* if (db_get-alloc(dbname, "a.0", &val, &vlen) == NULL)
* return -1;
* ..do stuff..
* if (val) free(val);
* @endcode
* @see db_get Pre-allocates memory
*/
int
db_get_alloc(char *file,
char *key,
void **data,
size_t *datalen)
{
DEPOT *dp;
int len;
/* Open database for writing */
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "%s: dpopen(%s): %s",
__FUNCTION__,
file,
dperrmsg(dpecode));
return -1;
}
if ((*data = dpget(dp, key, -1, 0, -1, &len)) == NULL){
if (dpecode == DP_ENOITEM){
*datalen = 0;
*data = NULL;
len = 0;
}
else{
/* No entry vs error? */
clicon_err(OE_DB, errno, "db_get_alloc: dpgetwb: %s (%d)",
dperrmsg(dpecode), dpecode);
dpclose(dp);
return -1;
}
}
*datalen = len;
if (dpclose(dp) == 0){
clicon_err(OE_DB, errno, "db_get_alloc: dpclose: %s", dperrmsg(dpecode));
return -1;
}
return 0;
}
/*! Delete database entry
* @param[in] file database file
* @param[in] key database key
* @retval -1 on failure,
* @retval 0 if key did not exist
* @retval 1 if successful.
*/
int
db_del(char *file, char *key)
{
int retval = 0;
DEPOT *dp;
/* Open database for writing */
if ((dp = dpopen(file, DP_OWRITER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "db_del: dpopen(%s): %s",
file,
dperrmsg(dpecode));
return -1;
}
if (dpout(dp, key, -1)) {
retval = 1;
}
if (dpclose(dp) == 0){
clicon_err(OE_DB, errno, "db_del: dpclose: %s", dperrmsg(dpecode));
return -1;
}
return retval;
}
/*! Check if entry in database exists
* @param[in] file database file
* @param[in] key database key
* @retval 1 if key exists in database
* @retval 0 key does not exist in database
* @retval -1 error
*/
int
db_exists(char *file,
char *key)
{
DEPOT *dp;
int len;
/* Open database for reading */
if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "%s: dpopen: %s",
__FUNCTION__, dperrmsg(dpecode));
return -1;
}
len = dpvsiz(dp, key, -1);
if (len < 0 && dpecode != DP_ENOITEM)
clicon_err(OE_DB, errno, "^s: dpvsiz: %s (%d)",
__FUNCTION__, dperrmsg(dpecode), dpecode);
if (dpclose(dp) == 0) {
clicon_err(OE_DB, errno, "%s: dpclose: %s", dperrmsg(dpecode),__FUNCTION__);
return -1;
}
return (len < 0) ? 0 : 1;
}
/*! Return all entries in database that match a regular expression.
* @param[in] file database file
* @param[in] regexp regular expression for database keys
* @param[in] label for memory/chunk allocation
* @param[out] pairs Vector of database keys and values
* @param[in] noval If set don't retreive values, just keys
* @retval -1 on error
* @retval n Number of pairs
* @code
* struct db_pair *pairs;
* int npairs;
* if ((npairs = db_regexp(dbname, "^/test/kalle$", __FUNCTION__,
* &pairs, 0)) < 0)
* err;
*
* @endcode
*/
int
db_regexp(char *file,
char *regexp,
const char *label,
struct db_pair **pairs,
int noval)
{
int npairs;
int status;
int retval = -1;
int vlen = 0;
char *key = NULL;
void *val = NULL;
char errbuf[512];
struct db_pair *pair;
struct db_pair *newpairs;
regex_t iterre;
DEPOT *iterdp = NULL;
regmatch_t pmatch[1];
size_t nmatch = 1;
npairs = 0;
*pairs = NULL;
if (regexp) {
if ((status = regcomp(&iterre, regexp, REG_EXTENDED)) != 0) {
regerror(status, &iterre, errbuf, sizeof(errbuf));
clicon_err(OE_DB, errno, "%s: regcomp: %s", __FUNCTION__, errbuf);
return -1;
}
}
/* Open database for reading */
if ((iterdp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, 0, "%s: dpopen(%s): %s",
__FUNCTION__, file, dperrmsg(dpecode));
goto quit;
}
/* Initiate iterator */
if(dpiterinit(iterdp) == 0) {
clicon_err(OE_DB, errno, "%s: dpiterinit: %s", __FUNCTION__, dperrmsg(dpecode));
goto quit;
}
/* Iterate through DB */
while((key = dpiternext(iterdp, NULL)) != NULL) {
if (regexp && regexec(&iterre, key, nmatch, pmatch, 0) != 0) {
free(key);
continue;
}
/* Retrieve value if required */
if ( ! noval) {
if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) {
clicon_log(LOG_WARNING, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode));
goto quit;
}
}
/* Resize and populate resulting array */
newpairs = rechunk(*pairs, (npairs+1) * sizeof(struct db_pair), label);
if (newpairs == NULL) {
clicon_err(OE_DB, errno, "%s: rechunk", __FUNCTION__);
goto quit;
}
pair = &newpairs[npairs];
memset(pair, 0, sizeof(*pair));
pair->dp_key = chunk_sprintf(label, "%s", key);
if (regexp)
pair->dp_matched = chunk_sprintf(label, "%.*s",
pmatch[0].rm_eo - pmatch[0].rm_so,
key + pmatch[0].rm_so);
else
pair->dp_matched = chunk_sprintf(label, "%s", key);
if (pair->dp_key == NULL || pair->dp_matched == NULL) {
clicon_err(OE_DB, errno, "%s: chunk_sprintf");
goto quit;
}
if ( ! noval) {
if (vlen){
pair->dp_val = chunkdup(val, vlen, label);
if (pair->dp_val == NULL) {
clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__);
goto quit;
}
}
pair->dp_vlen = vlen;
free(val);
val = NULL;
}
(*pairs) = newpairs;
npairs++;
free(key);
}
retval = npairs;
quit:
if (key)
free(key);
if (val)
free(val);
if (regexp)
regfree(&iterre);
if (iterdp)
dpclose(iterdp);
if (retval < 0)
unchunk_group(label);
return retval;
}
/*! Sanitize regexp string. Escape '\' etc.
*/
char *
db_sanitize(char *rx, const char *label)
{
char *new;
char *k, *p, *s;
k = chunk_sprintf(__FUNCTION__, "%s", "");
p = rx;
while((s = strstr(p, "\\"))) {
if ((k = chunk_sprintf(__FUNCTION__, "%s%.*s\\\\", k, s-p, p)) == NULL)
goto quit;
p = s+1;
}
if ((k = chunk_strncat(k, p, strlen(p), __FUNCTION__)) == NULL)
goto quit;
new = (char *)chunkdup(k, strlen(k)+1, label);
unchunk_group(__FUNCTION__);
return new;
quit:
unchunk_group(__FUNCTION__);
return NULL;
}
#if 0 /* Test program */
/*
* Turn this on to get an xpath test program
* Usage: clicon_xpath [<xpath>]
* read xml from input
* Example compile:
gcc -g -o qdb -I. -I../clixon ./clixon_qdb.c -lclixon -lcligen -lqdbm
*/
static int
usage(char *argv0)
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "\t%s init <filename>\n", argv0);
fprintf(stderr, "\t%s read <filename> <key>\n", argv0);
fprintf(stderr, "\t%s write <filename> <key> <val>\n", argv0);
fprintf(stderr, "\t%s openread <filename>\n", argv0);
fprintf(stderr, "\t%s openwrite <filename>\n", argv0);
exit(0);
}
int
main(int argc, char **argv)
{
char *verb;
char *filename;
char *key;
char *val;
size_t len;
DEPOT *dp;
if (argc < 3)
usage(argv[0]);
clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR);
verb = argv[1];
filename = argv[2];
if (strcmp(verb, "init")==0){
db_init(filename);
}
else if (strcmp(verb, "read")==0){
if (argc < 4)
usage(argv[0]);
key = argv[3];
db_get_alloc(filename, key, (void**)&val, &len);
fprintf(stdout, "%s\n", val);
}
else if (strcmp(verb, "write")==0){
if (argc < 5)
usage(argv[0]);
key = argv[3];
val = argv[4];
db_set(filename, key, val, strlen(val)+1);
}
else if (strcmp(verb, "openread")==0){
if ((dp = dpopen(filename, DP_OREADER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "dbopen: %s",
dperrmsg(dpecode));
return -1;
}
sleep(1000000);
}
else if (strcmp(verb, "openwrite")==0){
if ((dp = dpopen(filename, DP_OWRITER | DP_OLCKNB, 0)) == NULL){
clicon_err(OE_DB, errno, "dbopen: %s",
dperrmsg(dpecode));
return -1;
}
sleep(1000000);
}
return 0;
}
#endif /* Test program */

View file

@ -1,73 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifndef _CLIXON_QDB_H_
#define _CLIXON_QDB_H_
/*
* Low level API
*/
struct db_pair {
char *dp_key; /* database key */
char *dp_matched; /* Matched component of key */
char *dp_val; /* pointer to vector of lvalues */
int dp_vlen; /* length of vector of lvalues */
};
/*
* Prototypes
*/
int db_init(char *file);
int db_delete(char *file);
int db_set(char *file, char *key, void *data, size_t datalen);
int db_get(char *file, char *key, void *data, size_t *datalen);
int db_get_alloc(char *file, char *key, void **data, size_t *datalen);
int db_del(char *file, char *key);
int db_exists(char *file, char *key);
int db_regexp(char *file, char *regexp, const char *label,
struct db_pair **pairs, int noval);
char *db_sanitize(char *rx, const char *label);
#endif /* _CLIXON_QDB_H_ */

View file

@ -748,7 +748,8 @@ text_modify(cxobj *x0,
* @see text_modify
*/
static int
text_modify_top(cxobj *x0,
text_modify_top(struct text_handle *th,
cxobj *x0,
cxobj *x1,
yang_spec *yspec,
enum operation_type op,
@ -759,6 +760,7 @@ text_modify_top(cxobj *x0,
cxobj *x0c; /* base child */
cxobj *x1c; /* mod child */
yang_stmt *yc; /* yang child */
yang_stmt *ymod;/* yang module */
char *opstr;
/* Assure top-levels are 'config' */
@ -805,10 +807,23 @@ text_modify_top(cxobj *x0,
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c);
/* Get yang spec of the child */
if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){
clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?",
xml_name(x1), x1cname);
yc = NULL;
if (ys_module_by_xml(yspec, x1c, &ymod) <0)
goto done;
if (ymod != NULL)
yc = yang_find_datanode((yang_node*)ymod, x1cname);
if (yc == NULL && _CLICON_XML_NS_ITERATE){
int i;
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL)
break;
}
}
if (yc == NULL){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
goto done;
goto ok;
}
/* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, &x0c, yc) < 0)
@ -942,11 +957,12 @@ text_put(xmldb_handle xh,
xml_name(x0));
goto done;
}
/* Add yang specification backpointer to all XML nodes */
/* XXX: where is this created? Add yspec */
#if 0
/* Add yang specification backpointer to all XML nodes
* This is already done in from_client_edit_config() */
if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
#endif
#if 0 /* debug */
if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
@ -955,7 +971,7 @@ text_put(xmldb_handle xh,
* Modify base tree x with modification x1. This is where the
* new tree is made.
*/
if (text_modify_top(x0, x1, yspec, op, cbret) < 0)
if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret))

View file

@ -191,10 +191,9 @@ state data.
## Authentication and NACM
The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341):
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: adm1, wilma, and guest, according to the examples in Appendix A in the RFC.
* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341).
* A NACM backend plugin reporting the mandatory NACM state variables.
## Systemd files
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.

View file

@ -9,6 +9,7 @@ module example {
prefix ip;
}
import ietf-routing {
description "defines fib-route";
prefix rt;
}
import iana-if-type {
@ -72,4 +73,13 @@ module example {
}
}
}
rpc debug {
description "Set debug level of backend. XXX should be in clixon-config";
input {
leaf level {
type uint32;
}
}
}
}

View file

@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h,
/* User supplied variable in CLI command */
instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */
/* Create XML for fib-route netconf RPC */
if (xml_parse_va(&xtop, NULL, "<rpc username=\"%s\"><fib-route><routing-instance-name>%s</routing-instance-name></fib-route></rpc>",
if (xml_parse_va(&xtop, NULL, "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" username=\"%s\"><fib-route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\"><routing-instance-name>%s</routing-instance-name></fib-route></rpc>",
clicon_username_get(h),
cv_string_get(instance)) < 0)
goto done;

View file

@ -191,7 +191,7 @@ b64_decode(const char *src,
* @retval -1 Fatal error
* @retval 0 Unauth
* @retval 1 Auth
* @note: Three hardwired users: adm1, wilma, guest w password "bar".
* @note: Three hardwired users: andy, wilma, guest w password "bar".
* Enabled by passing -- -a to the main function
*/
int
@ -237,9 +237,9 @@ example_restconf_credentials(clicon_handle h,
/* Here get auth sub-tree whjere all the users are */
if ((cb = cbuf_new()) == NULL)
goto done;
/* Hardcoded user/passwd */
if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 ||
strcmp(user, "quest")==0){
/* XXX Three hardcoded user/passwd (from RFC8341 A.1)*/
if (strcmp(user, "wilma")==0 || strcmp(user, "andy")==0 ||
strcmp(user, "guest")==0){
passwd2 = "bar";
}
if (strcmp(passwd, passwd2))
@ -282,7 +282,7 @@ restconf_client_rpc(clicon_handle h,
/*! Start example restonf plugin. Set authentication method
* Arguments are argc/argv after --
* Currently defined: -a enable http basic authentication
* Note hardwired users adm1, wilma and guest
* @note There are three hardwired users andy, wilma and guest from RFC8341 A.1
*/
int
example_restconf_start(clicon_handle h,

View file

@ -36,7 +36,7 @@ This package contains header files for CLIXON.
%setup
%build
%configure --with-cligen=%{cligen_prefix} --without-keyvalue
%configure --with-cligen=%{cligen_prefix}
make
%install

View file

@ -80,5 +80,5 @@ distclean: clean
do (cd $$i; $(MAKE) $(MFLAGS) distclean); done; \
(cd clixon; $(MAKE) $(MFLAGS) $@)
tags:
TAGS:
find $(srcdir) -name '*.[chyl]' -print | etags -

View file

@ -36,9 +36,19 @@
#ifndef _CLIXON_NACM_H
#define _CLIXON_NACM_H
/*
* Constants
*/
/* RFC8341 defines a "recovery session" as outside the scope.
* Clixon defines this user as having special admin rights to expemt from
* all access control enforcements
*/
#define NACM_RECOVERY_USER "_nacm_recovery"
/*
* Prototypes
*/
int nacm_access(clicon_handle h, char *mode, char *name, char *username, cbuf *cbret);
int nacm_access(clicon_handle h, char *rpc, char *module,
char *username, cbuf *cbret);
#endif /* _CLIXON_NACM_H */

View file

@ -41,6 +41,10 @@
/*
* Constants
*/
/* If rpc call does not have a namespace (eg w xmlns) then use the default NETCONF
* namespace (rfc6241 3.1)
*/
#define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0"
/*
* Types
@ -81,10 +85,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg);
#define XML_FLAG_NONE 0x10 /* Node is added as NONE */
/* Sort and binary search of XML children
* Experimental
/* Iterate through modules to find the matching datanode
* or rpc if no xmlns attribute specifies namespace.
* This is loose semantics of finding namespaces.
* And it is wrong, but is the way Clixon originally was written."
* @see CLICON_XML_NS_ITERATE clixon configure option
*/
extern int xml_child_sort;
extern int _CLICON_XML_NS_ITERATE;
/*
* Prototypes
@ -94,6 +101,7 @@ char *xml_name(cxobj *xn);
int xml_name_set(cxobj *xn, char *name);
char *xml_namespace(cxobj *xn);
int xml_namespace_set(cxobj *xn, char *name);
int xml2ns(cxobj *x, char *localname, char **namespace);
cxobj *xml_parent(cxobj *xn);
int xml_parent_set(cxobj *xn, cxobj *parent);
@ -129,6 +137,8 @@ int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
char *xml_body(cxobj *xn);
cxobj *xml_body_get(cxobj *xn);
char *xml_find_type_value(cxobj *xn_parent, char *prefix,
char *name, enum cxobj_type type);
char *xml_find_value(cxobj *xn_parent, char *name);
char *xml_find_body(cxobj *xn, char *name);
cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val);

View file

@ -43,6 +43,7 @@
*/
int xml2txt(FILE *f, cxobj *x, int level);
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
int xml_yang_validate_rpc(cxobj *xrpc);
int xml_yang_validate_add(cxobj *xt, void *arg);
int xml_yang_validate_all(cxobj *xt, void *arg);
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
@ -60,6 +61,7 @@ int xml_default(cxobj *x, void *arg);
int xml_order(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, void *arg);
int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec);
int xml_spec_populate(cxobj *x, void *arg);
int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath);
int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath);

View file

@ -49,6 +49,7 @@
/*
* Types
*/
struct xml;
/*! YANG keywords from RFC6020.
* See also keywords generated by yacc/bison in clicon_yang_parse.tab.h, but they start with K_
* instead of Y_
@ -159,7 +160,6 @@ typedef enum yang_class yang_class;
*/
#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES)
/* Yang schema node .
* See RFC 7950 Sec 3:
* o schema node: A node in the schema tree. One of action, container,
@ -253,15 +253,18 @@ char *yang_key2str(int keyword);
char *yarg_prefix(yang_stmt *ys);
char *yarg_id(yang_stmt *ys);
int yang_nodeid_split(char *nodeid, char **prefix, char **id);
int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp);
yang_stmt *ys_module(yang_stmt *ys);
yang_spec *ys_spec(yang_stmt *ys);
yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix);
yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace);
yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument);
int yang_match(yang_node *yn, int keyword, char *argument);
yang_stmt *yang_find_datanode(yang_node *yn, char *argument);
yang_stmt *yang_find_schemanode(yang_node *yn, char *argument);
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class);
char *yang_find_myprefix(yang_stmt *ys);
char *yang_find_mynamespace(yang_stmt *ys);
int yang_order(yang_stmt *y);
int yang_print(FILE *f, yang_node *yn);
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);

View file

@ -90,22 +90,43 @@ enum childtype{
ANY_CHILD, /* eg <a><b/></a> or <a><b/><c/></a> */
};
/*! Number of children EXCEPT attributes
* @param[in] xn xml node
* @retval number of children in XML tree (except children of type CX_ATTR)
* @see xml_child_nr
*/
static int
xml_child_nr_noattr(cxobj *xn)
{
cxobj *x = NULL;
int nr = 0;
while ((x = xml_child_each(xn, x, -1)) != NULL) {
if (xml_type(x) != CX_ATTR)
nr++;
}
return nr;
}
/*! x is element and has exactly one child which in turn has none
* remove attributes from x
* Clone from clixon_xml_map.c
*/
static enum childtype
childtype(cxobj *x)
{
cxobj *xc1; /* the only child of x */
int clen; /* nr of children */
clen = xml_child_nr_noattr(x);
if (xml_type(x) != CX_ELMNT)
return -1; /* n/a */
if (xml_child_nr(x) == 0)
if (clen == 0)
return NULL_CHILD;
if (xml_child_nr(x) > 1)
if (clen > 1)
return ANY_CHILD;
xc1 = xml_child_i(x, 0); /* From here exactly one child */
if (xml_child_nr(xc1) == 0 && xml_type(xc1)==CX_BODY)
if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY)
return BODY_CHILD;
else
return ANY_CHILD;
@ -267,13 +288,13 @@ json_str_escape(char *str)
+----------+--------------+--------------+--------------+
*/
static int
xml2json1_cbuf(cbuf *cb,
cxobj *x,
xml2json1_cbuf(cbuf *cb,
cxobj *x,
enum array_element_type arraytype,
int level,
int pretty,
int flat,
int bodystr)
int level,
int pretty,
int flat,
int bodystr)
{
int retval = -1;
int i;
@ -281,10 +302,30 @@ xml2json1_cbuf(cbuf *cb,
enum childtype childt;
enum array_element_type xc_arraytype;
yang_stmt *ys;
yang_stmt *ymod; /* yang module */
yang_spec *yspec = NULL; /* yang spec */
int bodystr0=1;
char *str;
char *prefix=NULL; /* prefix / local namespace name */
char *namespace=NULL; /* namespace uri */
char *modname=NULL; /* Module name */
/* If x is labelled with a default namespace, it should be translated
* to a module name.
* Harder if x has a prefix, then that should also be translated to associated
* module name
*/
prefix = xml_namespace(x);
if (xml2ns(x, prefix, &namespace) < 0)
goto done;
if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */
yspec = ys_spec(ys);
/* Find module name associated with namspace URI */
if (namespace && yspec &&
(ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){
modname = ymod->ys_argument;
}
childt = childtype(x);
ys = xml_spec(x);
if (pretty==2)
cprintf(cb, "#%s_array, %s_child ",
arraytype2str(arraytype),
@ -292,7 +333,6 @@ xml2json1_cbuf(cbuf *cb,
switch(arraytype){
case BODY_ARRAY:{
if (bodystr){
char *str;
if ((str = json_str_escape(xml_value(x))) == NULL)
goto done;
cprintf(cb, "\"%s\"", str);
@ -300,14 +340,13 @@ xml2json1_cbuf(cbuf *cb,
}
else
cprintf(cb, "%s", xml_value(x));
break;
}
case NO_ARRAY:
if (!flat){
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
if (xml_namespace(x))
cprintf(cb, "%s:", xml_namespace(x));
if (modname) /* XXX should remove this? */
cprintf(cb, "%s:", modname);
cprintf(cb, "%s\": ", xml_name(x));
}
switch (childt){
@ -326,8 +365,8 @@ xml2json1_cbuf(cbuf *cb,
case FIRST_ARRAY:
case SINGLE_ARRAY:
cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, "");
if (xml_namespace(x))
cprintf(cb, "%s:", xml_namespace(x));
if (modname)
cprintf(cb, "%s:", modname);
cprintf(cb, "%s\": ", xml_name(x));
level++;
cprintf(cb, "[%s%*s",
@ -368,7 +407,7 @@ xml2json1_cbuf(cbuf *cb,
break;
}
/* Check for typed sub-body if:
* arracytype=* but chilt-type is BODY_CHILD
* arraytype=* but child-type is BODY_CHILD
* This is code for writing <a>42</a> as "a":42 and not "a":"42"
*/
if (childt == BODY_CHILD && ys!=NULL &&
@ -393,6 +432,8 @@ xml2json1_cbuf(cbuf *cb,
for (i=0; i<xml_child_nr(x); i++){
xc = xml_child_i(x, i);
if (xml_type(xc) == CX_ATTR)
continue; /* XXX Only xmlns attributes mapped */
xc_arraytype = array_eval(i?xml_child_i(x,i-1):NULL,
xc,
xml_child_i(x, i+1));
@ -401,7 +442,7 @@ xml2json1_cbuf(cbuf *cb,
xc_arraytype,
level+1, pretty, 0, bodystr0) < 0)
goto done;
if (i<xml_child_nr(x)-1)
if (i<xml_child_nr_noattr(x)-1)
cprintf(cb, ",%s", pretty?"\n":"");
}
switch (arraytype){

View file

@ -128,27 +128,28 @@ nacm_match_access(char *access_operations,
static int
nacm_match_rule(clicon_handle h,
char *name,
char *module,
cxobj *xrule,
cbuf *cbret)
{
int retval = -1;
// cxobj *x;
char *module_name;
char *rpc_name;
char *module_rule; /* rule module name */
char *rpc_rule;
char *access_operations;
char *action;
module_name = xml_find_body(xrule, "module-name");
rpc_name = xml_find_body(xrule, "rpc-name");
module_rule = xml_find_body(xrule, "module-name");
rpc_rule = xml_find_body(xrule, "rpc-name");
/* XXX access_operations can be a set of bits */
access_operations = xml_find_body(xrule, "access-operations");
action = xml_find_body(xrule, "action");
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
module_name, rpc_name, access_operations, action);
if (module_name && strcmp(module_name,"*")==0){
module_rule, rpc_rule, access_operations, action);
if (module_rule &&
(strcmp(module_rule,"*")==0 || strcmp(module_rule,module)==0)){
if (nacm_match_access(access_operations, "exec")){
if (rpc_name==NULL ||
strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){
if (rpc_rule==NULL ||
strcmp(rpc_rule, "*")==0 || strcmp(rpc_rule, name)==0){
/* Here is a matching rule */
if (action && strcmp(action, "permit")==0){
retval = 1;
@ -166,61 +167,43 @@ nacm_match_rule(clicon_handle h,
retval = 2; /* no matching rule */
done:
return retval;
}
/*! Make nacm access control
/*! Process a nacm message control
* @param[in] h Clicon handle
* @param[in] mode NACMmode, internal or external
* @param[in] name rpc name
* @param[in] username
* @param[in] xtop
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
* @retval -1 Error
* @retval 0 Not access and cbret set
* @retval 1 Access
* @see RFC8341 3.4.4. Incoming RPC Message Validation
*/
int
nacm_access(clicon_handle h,
char *mode,
char *name,
char *username,
cbuf *cbret)
static int
nacm_rpc_validation(clicon_handle h,
char *name,
char *module,
char *username,
cxobj *xtop,
cbuf *cbret)
{
int retval = -1;
cxobj *xtop = NULL;
cxobj *xacm;
cxobj *x;
cxobj *xrlist;
cxobj *xrule;
char *enabled = NULL;
cxobj **gvec = NULL; /* groups */
size_t glen;
cxobj *xrlist;
cxobj **rlistvec = NULL; /* rule-list */
size_t rlistlen;
cxobj **rvec = NULL; /* rules */
size_t rlen;
int ret;
int i, j;
char *exec_default = NULL;
int ret;
clicon_debug(1, "%s", __FUNCTION__);
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
otherwise get it from internal configuration */
if (strcmp(mode, "external")==0){
if ((xtop = clicon_nacm_ext(h)) == NULL){
clicon_err(OE_XML, 0, "No nacm external tree");
goto done;
}
}
else if (strcmp(mode, "internal")==0){
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
goto done;
}
else{
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
goto done;
}
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
operation is permitted. (or config does not exist) */
@ -235,6 +218,8 @@ nacm_access(clicon_handle h,
/* 2. If the requesting session is identified as a recovery session,
then the protocol operation is permitted. NYI */
if (strcmp(username, NACM_RECOVERY_USER) == 0)
goto permit;
/* 3. If the requested operation is the NETCONF <close-session>
protocol operation, then the protocol operation is permitted.
@ -280,7 +265,7 @@ nacm_access(clicon_handle h,
for (j=0; j<rlen; j++){
xrule = rvec[j];
/* -1 error, 0 deny, 1 permit, 2 continue */
if ((ret = nacm_match_rule(h, name, xrule, cbret)) < 0)
if ((ret = nacm_match_rule(h, name, module, xrule, cbret)) < 0)
goto done;
switch(ret){
case 0: /* deny */
@ -318,8 +303,6 @@ nacm_access(clicon_handle h,
retval = 1;
done:
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
if (strcmp(mode, "internal")==0 && xtop)
xml_free(xtop);
if (gvec)
free(gvec);
if (rlistvec)
@ -332,3 +315,57 @@ nacm_access(clicon_handle h,
retval = 0;
goto done;
}
/*! Make nacm access control
* @param[in] h Clicon handle
* @param[in] name rpc name
* @param[in] username
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
* @retval -1 Error
* @retval 0 Not access and cbret set
* @retval 1 Access
* @see RFC8341 3.4.4. Incoming RPC Message Validation
*/
int
nacm_access(clicon_handle h,
char *rpc,
char *module,
char *username,
cbuf *cbret)
{
int retval = -1;
cxobj *xtop = NULL;
char *mode;
clicon_debug(1, "%s", __FUNCTION__);
mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode == NULL || strcmp(mode, "disabled") == 0){
retval = 1;
goto done;
}
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
otherwise get it from internal configuration */
if (strcmp(mode, "external")==0){
if ((xtop = clicon_nacm_ext(h)) == NULL){
clicon_err(OE_XML, 0, "No nacm external tree");
goto done;
}
}
else if (strcmp(mode, "internal")==0){
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
goto done;
}
else{
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
goto done;
}
/* Do the real nacm processing */
if ((retval = nacm_rpc_validation(h, rpc, module, username, xtop, cbret)) < 0)
goto done;
done:
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
if (strcmp(mode, "internal")==0 && xtop)
xml_free(xtop);
return retval;
}

View file

@ -983,19 +983,14 @@ netconf_module_load(clicon_handle h)
{
int retval = -1;
cxobj *xc;
// cxobj *x;
yang_spec *yspec;
yspec = clicon_dbspec_yang(h);
/* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done;
if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
goto done;
if ((xc = clicon_conf_xml(h)) == NULL){
clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
goto done;
}
/* Enable features (hardcoded here) */
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:candidate</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
@ -1005,6 +1000,12 @@ netconf_module_load(clicon_handle h)
goto done;
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
/* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done;
if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0)
goto done;
retval = 0;
done:
return retval;

View file

@ -63,6 +63,7 @@
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_sort.h"
#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
@ -242,6 +243,9 @@ clicon_options_main(clicon_handle h,
clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix);
goto done;
}
#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */
_CLICON_XML_NS_ITERATE = 1;
#endif
/* Read configfile first without yangspec, for bootstrapping */
if (parse_configfile(h, configfile, yspec, &xconfig) < 0)
goto done;
@ -267,6 +271,9 @@ clicon_options_main(clicon_handle h,
xml_child_sort = 1;
else
xml_child_sort = 0;
#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */
_CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE");
#endif
retval = 0;
done:
return retval;

View file

@ -826,7 +826,8 @@ clicon_rpc_debug(clicon_handle h,
char *username;
username = clicon_username_get(h);
if ((msg = clicon_msg_encode("<rpc username=\"%s\"><debug><level>%d</level></debug></rpc>", username?username:"", level)) == NULL)
/* XXX: hardcoded example yang, should be clixon-config!!! */
if ((msg = clicon_msg_encode("<rpc username=\"%s\"><debug xmlns=\"urn:example:clixon\"><level>%d</level></debug></rpc>", username?username:"", level)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;

View file

@ -108,17 +108,13 @@
* - Namespace name: For a name N in a namespace identified by a URI I, the
* "namespace name" is I.
* For a name N that is not in a namespace, the "namespace name" has no value.
* - Local name: In either case the "local name" is N.
* - Local name: In either case the "local name" is N (also "prefix")
* It is this combination of the universally managed URI namespace with the
* vocabulary's local names that is effective in avoiding name clashes.
*/
struct xml{
char *x_name; /* name of node */
char *x_namespace; /* namespace, if any */
#ifdef notyet
char *x_namespacename; /* namespace name (or NULL) */
char *x_localname; /* Local name N as defined above */
#endif
char *x_prefix; /* namespace localname N, called prefix */
struct xml *x_up; /* parent node in hierarchy if any */
struct xml **x_childvec; /* vector of children nodes */
int x_childvec_len;/* length of vector */
@ -130,6 +126,17 @@ struct xml{
reference, dont free */
};
/*
* Variables
*/
/* Iterate through modules to find the matching datanode
* or rpc if no xmlns attribute specifies namespace.
* This is loose semantics of finding namespaces.
* And it is wrong, but is the way Clixon originally was written."
* @see CLICON_XML_NS_ITERATE clixon configure option
*/
int _CLICON_XML_NS_ITERATE = 0;
/* Mapping between xml type <--> string */
static const map_str2int xsmap[] = {
{"error", CX_ERROR},
@ -189,29 +196,31 @@ xml_name_set(cxobj *xn,
/*! Get namespace of xnode
* @param[in] xn xml node
* @retval namespace of xml node
* XXX change to xml_localname
*/
char*
xml_namespace(cxobj *xn)
{
return xn->x_namespace;
return xn->x_prefix;
}
/*! Set name space of xnode, namespace is copied
* @param[in] xn xml node
* @param[in] namespace new namespace, null-terminated string, copied by function
* @param[in] localname new namespace, null-terminated string, copied by function
* @retval -1 on error with clicon-err set
* @retval 0 OK
* XXX change to xml_localname_set
*/
int
xml_namespace_set(cxobj *xn,
char *namespace)
char *localname)
{
if (xn->x_namespace){
free(xn->x_namespace);
xn->x_namespace = NULL;
if (xn->x_prefix){
free(xn->x_prefix);
xn->x_prefix = NULL;
}
if (namespace){
if ((xn->x_namespace = strdup(namespace)) == NULL){
if (localname){
if ((xn->x_prefix = strdup(localname)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
@ -219,12 +228,57 @@ xml_namespace_set(cxobj *xn,
return 0;
}
/*! See if xmlns:<namespace>=<uri> exists, if so return <uri>
/*! Given an xml tree return URI namespace: default or localname given
*
* Given an XML tree and a prefix (or NULL) return URI namespace.
* @param[in] x XML tree
* @param[in] prefix prefix/ns localname. If NULL then return default.
* @param[out] namespace URI namespace (or NULL). Note pointer into xml tree
* @retval 0 OK
* @retval -1 Error
* @see xmlns_check XXX coordinate
*/
int
xml2ns(cxobj *x,
char *prefix,
char **namespace)
{
int retval = -1;
char *ns;
cxobj *xp;
if (prefix != NULL) /* xmlns:<prefix> */
ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR);
else /* default ns */
ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR);
/* namespace not found, try parent */
if (ns == NULL){
if ((xp = xml_parent(x)) != NULL){
if (xml2ns(xp, prefix, &ns) < 0)
goto done;
}
/* If no parent, return default namespace if defined */
#if defined(DEFAULT_XML_RPC_NAMESPACE)
else
ns = DEFAULT_XML_RPC_NAMESPACE;
#endif
}
if (namespace)
*namespace = ns;
retval = 0;
done:
return retval;
}
/*! See if xmlns:[<localname>=]<uri> exists, if so return <uri>
*
* @param[in] xn XML node
* @param[in] nsn Namespace name
* @retval URI return associated URI if found
* @retval NULL No namespace name binding found for nsn
* @see xml2ns XXX coordinate
*/
static char *
xmlns_check(cxobj *xn,
@ -233,7 +287,7 @@ xmlns_check(cxobj *xn,
cxobj *x = NULL;
char *xns;
while ((x = xml_child_each(xn, x, -1)) != NULL)
while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL)
if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 &&
strcmp(xml_name(x), nsn) == 0)
return xml_value(x);
@ -248,7 +302,7 @@ xmlns_check(cxobj *xn,
* @note This function is grossly inefficient
*/
static int
xml_namespace_check(cxobj *xn,
xml_localname_check(cxobj *xn,
void *arg)
{
cxobj *xp = NULL;
@ -886,6 +940,42 @@ xml_body_get(cxobj *xt)
return NULL;
}
/*! Find and return the value of an xml child of specific type
*
* The value can be of an attribute only
* @param[in] xt xml tree node
* @param[in] prefix Prefix (namespace local name) or NULL
* @param[in] name name of xml tree node (eg attr name or "body")
* @retval val Pointer to the name string
* @retval NULL No such node or no value in node
* @code
* char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR);
* @endcode
* @note, make a copy of the return value to use it properly
* @see xml_find_value where a body can be found as well
*/
char *
xml_find_type_value(cxobj *xt,
char *prefix,
char *name,
enum cxobj_type type)
{
cxobj *x = NULL;
int pmatch; /* prefix match */
char *xprefix; /* xprefix */
while ((x = xml_child_each(xt, x, type)) != NULL) {
xprefix = xml_namespace(x);
if (prefix)
pmatch = xprefix?strcmp(prefix,xprefix)==0:0;
else
pmatch = 1;
if (pmatch && strcmp(name, xml_name(x)) == 0)
return xml_value(x);
}
return NULL;
}
/*! Find and return the value of a sub xml node
*
* The value can be of an attribute or body.
@ -895,7 +985,7 @@ xml_body_get(cxobj *xt)
* @retval NULL No such node or no value in node
*
* Note, make a copy of the return value to use it properly
* See also xml_find_body
* @see xml_find_body
* Explaining picture:
* xt --> x
* x_name=name
@ -983,8 +1073,8 @@ xml_free(cxobj *x)
free(x->x_name);
if (x->x_value)
free(x->x_value);
if (x->x_namespace)
free(x->x_namespace);
if (x->x_prefix)
free(x->x_prefix);
for (i=0; i<x->x_childvec_len; i++){
if ((xc = x->x_childvec[i]) != NULL){
xml_free(xc);
@ -1028,6 +1118,8 @@ clicon_xml2file(FILE *f,
char *val;
char *encstr = NULL; /* xml encoded string */
if (x == NULL)
goto ok;
name = xml_name(x);
namespace = xml_namespace(x);
switch(xml_type(x)){
@ -1097,6 +1189,7 @@ clicon_xml2file(FILE *f,
default:
break;
}/* switch */
ok:
retval = 0;
done:
if (encstr)
@ -1302,7 +1395,7 @@ _xml_parse(const char *str,
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
goto done;
/* Verify namespaces after parsing */
if (xml_apply0(xt, CX_ELMNT, xml_namespace_check, NULL) < 0)
if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0)
goto done;
/* Sort the complete tree after parsing */
if (yspec){
@ -1507,15 +1600,20 @@ int
xml_copy_one(cxobj *x0,
cxobj *x1)
{
char *s;
xml_type_set(x1, xml_type(x0));
if (xml_value(x0)){ /* malloced string */
if ((x1->x_value = strdup(x0->x_value)) == NULL){
if ((s = xml_value(x0))){ /* malloced string */
if ((x1->x_value = strdup(s)) == NULL){
clicon_err(OE_XML, errno, "strdup");
return -1;
}
}
if (xml_name(x0)) /* malloced string */
if ((xml_name_set(x1, xml_name(x0))) < 0)
if ((s = xml_name(x0))) /* malloced string */
if ((xml_name_set(x1, s)) < 0)
return -1;
if ((s = xml_namespace(x0))) /* malloced string */
if ((xml_namespace_set(x1, s)) < 0)
return -1;
return 0;
}
@ -1794,7 +1892,6 @@ xml_body_parse(cxobj *xb,
if (retval < 0 && cv != NULL)
cv_free(cv);
return retval;
}
/*! Parse an xml body as int32

View file

@ -70,7 +70,7 @@
/*! Load an xmldb storage plugin according to filename
* If init function fails (not found, wrong version, etc) print a log and dont
* add it.
* @param[in] h CLicon handle
* @param[in] h Clicon handle
* @param[in] filename Actual filename including path
*/
int

View file

@ -245,7 +245,7 @@ validate_leafref(cxobj *xt,
char *leafbody;
if ((leafrefbody = xml_body(xt)) == NULL)
return 0;
goto ok;
if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){
clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument);
goto done;
@ -264,6 +264,7 @@ validate_leafref(cxobj *xt,
leafrefbody);
goto done;
}
ok:
retval = 0;
done:
if (xvec)
@ -337,6 +338,73 @@ validate_identityref(cxobj *xt,
return retval;
}
/*! Validate an RPC node
* @param[in] xt XML node to be validated
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* rfc7950
* 7.14.2
* If a leaf in the input tree has a "mandatory" statement with the
* value "true", the leaf MUST be present in an RPC invocation.
*
* If a leaf in the input tree has a default value, the server MUST use
* this value in the same cases as those described in Section 7.6.1. In
* these cases, the server MUST operationally behave as if the leaf was
* present in the RPC invocation with the default value as its value.
*
* If a leaf-list in the input tree has one or more default values, the
* server MUST use these values in the same cases as those described in
* Section 7.7.2. In these cases, the server MUST operationally behave
* as if the leaf-list was present in the RPC invocation with the
* default values as its values.
*
* Since the input tree is not part of any datastore, all "config"
* statements for nodes in the input tree are ignored.
*
* If any node has a "when" statement that would evaluate to "false",
* then this node MUST NOT be present in the input tree.
*
* 7.14.4
* Input parameters are encoded as child XML elements to the rpc node's
* XML element, in the same order as they are defined within the "input"
* statement.
*
* If the RPC operation invocation succeeded and no output parameters
* are returned, the <rpc-reply> contains a single <ok/> element defined
* in [RFC6241]. If output parameters are returned, they are encoded as
* child elements to the <rpc-reply> element defined in [RFC6241], in
* the same order as they are defined within the "output" statement.
*/
int
xml_yang_validate_rpc(cxobj *xrpc)
{
int retval = -1;
yang_stmt *yn=NULL; /* rpc name */
cxobj *xn; /* rpc name */
yang_stmt *yi=NULL; /* input name */
cxobj *xi; /* input name */
assert(strcmp(xml_name(xrpc), "rpc")==0);
xn = NULL;
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
if ((yn = xml_spec(xn)) == NULL)
goto fail;
xi = NULL;
while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) {
if ((yi = xml_spec(xi)) == NULL)
goto fail;
}
}
// ok: /* pass validation */
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification for added entry
* 1. Check if mandatory leafs present as subs.
* 2. Check leaf values, eg int ranges and string regexps.
@ -418,9 +486,14 @@ xml_yang_validate_add(cxobj *xt,
/*! Validate a single XML node with yang specification for all (not only added) entries
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
* @param[in] xt XML node to be validated
* @retval 0 Valid OK
* @param[in] arg Not used
* @retval -1 Validation failed
* @retval 0 Validation OK
* @see xml_yang_validate_add
* @code
* if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0)
* err;
* @endcode
*/
int
xml_yang_validate_all(cxobj *xt,
@ -1397,41 +1470,133 @@ xml_non_config_data(cxobj *xt,
return retval;
}
/*! Add yang specification backpoint to XML node
/*! Add yang specification backpointer to rpc
*
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @note This may be unnecessary if yspec us set on creation
* @retval 0 OK
* @retval -1 Error
* @note This may be unnecessary if yspec is set on creation
* @note For subs to anyxml nodes will not have spec set
* @note No validation is done,... XXX
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode
* @see xml_spec_populate
*/
int
xml_spec_populate_rpc(clicon_handle h,
cxobj *xrpc,
yang_spec *yspec)
{
int retval = -1;
yang_stmt *y=NULL; /* yang node */
yang_stmt *ymod=NULL; /* yang module */
yang_stmt *yi = NULL; /* input */
yang_stmt *ya = NULL; /* arg */
cxobj *x;
cxobj *xi;
int i;
if ((strcmp(xml_name(xrpc), "rpc"))!=0){
clicon_err(OE_UNIX, EINVAL, "RPC expected");
goto done;
}
x = NULL;
while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) {
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
if (ymod != NULL)
y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x));
/* Loose semantics: loop through all modules to find the node
*/
if (y == NULL &&
clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL)
break;
}
}
if (y){
xml_spec_set(x, y);
if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){
xi = NULL;
while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) {
if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL)
xml_spec_set(xi, ya);
}
}
}
}
retval = 0;
done:
return retval;
}
/*! Add yang specification backpointer to XML node
* @param[in] xt XML tree node
* @param[in] arg Yang spec
* @note This may be unnecessary if yspec is set on creation
* @note For subs to anyxml nodes will not have spec set
* @note No validation is done,... XXX
* @note relies on kludge _CLICON_XML_NS_ITERATE
* @code
* xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec)
* @endcode
*/
int
xml_spec_populate(cxobj *x,
void *arg)
{
int retval = -1;
yang_spec *yspec = (yang_spec*)arg;
// clicon_handle h = (clicon_handle)arg;
yang_spec *yspec=NULL; /* yang spec */
yang_stmt *y=NULL; /* yang node */
yang_stmt *yparent; /* yang parent */
yang_stmt *ymod; /* yang module */
cxobj *xp; /* xml parent */
char *name;
int i;
if (xml_child_spec(xml_name(x), xml_parent(x), yspec, &y) < 0)
goto done;
#if 0
if ((xp = xml_parent(x)) != NULL &&
(yp = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yp, xml_name(x));
else
y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
#endif
if (y)
yspec = (yang_spec*)arg;
if (xml_spec(x))
goto ok;
xp = xml_parent(x);
name = xml_name(x);
if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yparent, name);
else if (yspec){
if (ys_module_by_xml(yspec, x, &ymod) < 0)
goto done;
if (ymod != NULL)
y = yang_find_datanode((yang_node*)ymod, name);
/* Loose semantics: loop through all modules to find the node
* XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE")
*/
if (y == NULL && _CLICON_XML_NS_ITERATE){
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL)
break;
}
}
}
if (y)
xml_spec_set(x, y);
#if 0 /* Add if you want validation error */
else {
clicon_err(OE_YANG, ENOENT, "No yang top found?");
goto done;
}
#endif
ok:
retval = 0;
done:
return retval;
}
/*! Translate from restconf api-path in cvv form to xml xpath
* eg a/b=c -> a/[b=c]
* @param[in] yspec Yang spec
@ -1757,7 +1922,6 @@ api_path2xml(char *api_path,
* @retval 0 OK. If reason is set, Yang error
* @retval -1 Error
* Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c
*/
static int
xml_merge1(cxobj *x0,

View file

@ -56,7 +56,6 @@
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
@ -73,7 +72,6 @@
*/
int xml_child_sort = 1;
/*! Given a child name and an XML object, return yang stmt of child
* If no xml parent, find root yang stmt matching name
* @param[in] x Child
@ -81,6 +79,8 @@ int xml_child_sort = 1;
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
* @note works for import prefix, but not work for generic XML parsing where
* xmlns and xmlns:ns are used.
*/
int
xml_child_spec(char *name,
@ -88,17 +88,40 @@ xml_child_spec(char *name,
yang_spec *yspec,
yang_stmt **yresult)
{
yang_stmt *y; /* result yang node */
int retval = -1;
yang_stmt *y = NULL; /* result yang node */
yang_stmt *yparent; /* parent yang */
if (xp && (yparent = xml_spec(xp)) != NULL)
y = yang_find_datanode((yang_node*)yparent, name);
else if (yspec)
y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */
yang_stmt *ymod = NULL;
yang_stmt *yi;
int i;
if (xp && (yparent = xml_spec(xp)) != NULL){
if (yparent->ys_keyword == Y_RPC){
if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL)
y = yang_find_datanode((yang_node*)yi, name);
}
else
y = yang_find_datanode((yang_node*)yparent, name);
}
else if (yspec){
if (ys_module_by_xml(yspec, xp, &ymod) < 0)
goto done;
if (ymod != NULL)
y = yang_find_schemanode((yang_node*)ymod, name);
if (y == NULL && _CLICON_XML_NS_ITERATE){
for (i=0; i<yspec->yp_len; i++){
ymod = yspec->yp_stmt[i];
if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL)
break;
}
}
}
else
y = NULL;
*yresult = y;
return 0;
retval = 0;
done:
return retval;
}
/*! Help function to qsort for sorting entries in xml child vector

View file

@ -658,15 +658,18 @@ yang_find_schemanode(yang_node *yn,
return ysmatch;
}
/*! Find first matching data node in all (sub)modules in a yang spec
/*! Find first matching data node in all modules in a yang spec (prefixes)
*
* @param[in] ysp Yang specification
* @param[in] argument Name of node. If NULL match first
* @param[in] nodeid Name of node. If NULL match first
* @param[in] class See yang_class for class of yang nodes
* A yang specification has modules as children which in turn can have
* syntax-nodes as children. This function goes through all the modules to
* look for nodes. Note that if a child to a module is a choice,
* the search is made recursively made to the choice's children.
* @note works for import prefix, but not work for generic XML parsing where
* xmlns and xmlns:ns are used.
* @see yang_find_top_ns
*/
yang_stmt *
yang_find_topnode(yang_spec *ysp,
@ -677,7 +680,7 @@ yang_find_topnode(yang_spec *ysp,
yang_stmt *yres = NULL; /* result */
char *prefix = NULL;
char *id = NULL;
int i;
int i;
if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
goto done;
@ -719,7 +722,7 @@ yang_find_topnode(yang_spec *ysp,
}
/*! Given a yang statement, find the prefix associated to this module
* @param[in] ys Yang statement
* @param[in] ys Yang statement in module tree (or module itself)
* @retval NULL Not found
* @retval prefix Prefix as char* pointer into yang tree
* @code
@ -745,6 +748,34 @@ yang_find_myprefix(yang_stmt *ys)
return prefix;
}
/*! Given a yang statement, find the namespace URI associated to this module
* @param[in] ys Yang statement in module tree (or module itself)
* @retval NULL Not found
* @retval namespace Namspace URI as char* pointer into yang tree
* @code
* char *myns = yang_find_mynamespace(ys);
* @endcode
* @see yang_find_module_by_namespace
*/
char *
yang_find_mynamespace(yang_stmt *ys)
{
yang_stmt *ymod; /* My module */
yang_stmt *ynamespace;
char *namespace = NULL;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "My yang module not found");
goto done;
}
if ((ynamespace = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) == NULL)
goto done;
namespace = ynamespace->ys_argument;
done:
return namespace;
}
/*! Find matching y in yp:s children, return 0 and index or -1 if not found.
* @retval 0 not found
* @retval 1 found
@ -811,7 +842,52 @@ yang_key2str(int keyword)
return (char*)clicon_int2str(ykmap, keyword);
}
/*! Find top module or sub-module given a statement.
/*! Find top data node among all modules by namespace in xml tree
* @param[in] ysp Yang specification
* @param[in] xt XML node
* @param[out] ymod Yang module (NULL if not found)
* @retval 0 OK
* @retval -1 Error
* @note works for xml namespaces (xmlns / xmlns:ns)
*/
int
ys_module_by_xml(yang_spec *ysp,
cxobj *xt,
yang_stmt **ymodp)
{
int retval = -1;
yang_stmt *ym = NULL; /* module */
char *prefix = NULL;
char *namespace = NULL; /* namespace URI */
if (ymodp)
*ymodp = NULL;
prefix = xml_namespace(xt);
if (prefix){
/* Get namespace for prefix */
if (xml2ns(xt, prefix, &namespace) < 0)
goto done;
}
else{
/* Get default namespace */
if (xml2ns(xt, NULL, &namespace) < 0)
goto done;
}
/* No namespace found, give up */
if (namespace == NULL)
goto ok;
/* We got the namespace, now get the module */
ym = yang_find_module_by_namespace(ysp, namespace);
/* Set result param */
if (ymodp && ym)
*ymodp = ym;
ok:
retval = 0;
done:
return retval;
}
/*! Find the top module or sub-module given a statement from within a yang tree
* Ultimate top is yang spec, dont return that
* The routine recursively finds ancestors.
* @param[in] ys Any yang statement in a yang tree
@ -840,7 +916,7 @@ ys_module(yang_stmt *ys)
return ys;
}
/*! Find top of tree, the yang specification
/*! Find top of tree, the yang specification from within the tree
* @param[in] ys Any yang statement in a yang tree
* @retval yspec The top yang specification
* @see ys_module
@ -1005,6 +1081,29 @@ yang_find_module_by_prefix(yang_stmt *ys,
return ymod;
}
/*! Given a yang statement and a namespace, return yang module
*
* @param[in] yspec A yang specification
* @param[in] namespace namespace
* @retval ymod Yang module statement if found
* @retval NULL not found
*/
yang_stmt *
yang_find_module_by_namespace(yang_spec *yspec,
char *namespace)
{
yang_stmt *ymod = NULL;
if (namespace == NULL)
goto done;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (yang_find((yang_node*)ymod, Y_NAMESPACE, namespace) != NULL)
break;
}
done:
return ymod;
}
/*! string is quoted if it contains space or tab, needs double '' */
static int inline
quotedstring(char *s)
@ -1984,6 +2083,7 @@ yang_parse_filename(const char *filename,
int fd = -1;
struct stat st;
// clicon_debug(1, "%s %s", __FUNCTION__, filename);
if (stat(filename, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", filename);
goto done;
@ -2903,6 +3003,8 @@ yang_spec_load_dir(clicon_handle h,
len = b-base;
else
len = strlen(base);
/* remove duplicates: there may be cornercases that dont work, eg
* mix of revisions and not? */
for (j = (i+1); j < ndp; j++)
if (strncmp(base, dp[j].d_name, len) == 0)
break;

View file

@ -7,6 +7,12 @@
testnr=0
testname=
# If set to 0, override starting of clixon_backend in test (you bring your own)
: ${BE:=1}
# If set, enable debugging (of backend)
: ${DBG:=0}
# For memcheck
#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli"
clixon_cli=clixon_cli
@ -56,6 +62,7 @@ new(){
testname=$1
>&2 echo "Test$testnr [$1]"
}
# No CR
new2(){
testnr=`expr $testnr + 1`
testname=$1
@ -84,7 +91,7 @@ expectfn(){
# echo "retval:\"$retval\""
# echo "ret:\"$ret\""
# echo "r:\"$r\""
if [ $r != $retval ]; then
if [ $r != $retval ]; then
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
echo -e "\e[0m:"
exit -1
@ -219,3 +226,29 @@ expectwait(){
fi
}
expectmatch(){
ret=$1
r=$2
expret=$3
expect=$4
if [ $r != $expret ]; then
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
echo -e "\e[0m:"
exit -1
fi
if [ -z "$ret" -a -z "$expect" ]; then
echo > /dev/null
else
match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
if [ -n "$expect2" ]; then
match=`echo "$ret" | grep -EZo "$expect2"`
if [ -z "$match" ]; then
err $expect "$ret"
fi
fi
fi
}

47
test/nacm.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
# Authentication and authorization and IETF NACM
# Library variable and functions
USER=$(whoami)
# Three groups from RFC8341 A.1 (admin extended with $USER)
NGROUPS=$(cat <<EOF
<groups>
<group>
<name>admin</name>
<user-name>admin</user-name>
<user-name>andy</user-name>
<user-name>$USER</user-name>
</group>
<group>
<name>limited</name>
<user-name>wilma</user-name>
<user-name>bam-bam</user-name>
</group>
<group>
<name>guest</name>
<user-name>guest</user-name>
<user-name>guest@example.com</user-name>
</group>
</groups>
EOF
)
# Permit all rule for admin group from RFC8341 A.2
NADMIN=$(cat <<EOF
<rule-list>
<name>admin-acl</name>
<group>admin</group>
<rule>
<name>permit-all</name>
<module-name>*</module-name>
<access-operations>*</access-operations>
<action>permit</action>
<comment>
Allow the 'admin' group complete access to all operations and data.
</comment>
</rule>
</rule-list>
EOF
)

View file

@ -14,7 +14,7 @@ APPNAME=example
cfg=$dir/conf_yang.xml
cat <<EOF > $cfg
<config>
<config xmlns="http://clicon.org">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
@ -31,17 +31,18 @@ cat <<EOF > $cfg
</config>
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
sudo $clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "cli configure top"
@ -114,6 +115,10 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$"
new "cli rpc"
expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "<address-family>ipv4</address-family>" "<next-hop-list>2.3.4.5</next-hop-list>"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -46,7 +46,12 @@ module ietf-ip{
}
EOF
db='<config><x><y><a>1</a><b>2</b><c>first-entry</c></y><y><a>1</a><b>3</b><c>second-entry</c></y><y><a>2</a><b>3</b><c>third-entry</c></y><d/><f><e>a</e><e>b</e><e>c</e></f><g>astring</g></x></config>'
xml='<config><x xmlns="urn:example:clixon"><y><a>1</a><b>2</b><c>first-entry</c></y><y><a>1</a><b>3</b><c>second-entry</c></y><y><a>2</a><b>3</b><c>third-entry</c></y><d/><f><e>a</e><e>b</e><e>c</e></f><g>astring</g></x></config>'
# Without xmlns
xmlxxx='<config><x><y><a>1</a><b>2</b><c>first-entry</c></y><y><a>1</a><b>3</b><c>second-entry</c></y><y><a>2</a><b>3</b><c>third-entry</c></y><d/><f><e>a</e><e>b</e><e>c</e></f><g>astring</g></x></config>'
run(){
name=$1
@ -62,12 +67,12 @@ run(){
new "datastore $name init"
expectfn "$datastore $conf init" 0 ""
# Whole tree operations
new "datastore $name put all replace"
expectfn "$datastore $conf put replace $db" 0 ""
ret=$($datastore $conf put replace "$xml")
expectmatch "$ret" $? "0" ""
new "datastore $name get"
expectfn "$datastore $conf get /" 0 "^$db$"
expectfn "$datastore $conf get /" 0 "^$xmlxxx$"
new "datastore $name put all remove"
expectfn "$datastore $conf put remove <config/>" 0 ""
@ -76,10 +81,13 @@ run(){
expectfn "$datastore $conf get /" 0 "^<config/>$"
new "datastore $name put all merge"
expectfn "$datastore $conf put merge $db" 0 ""
ret=$($datastore $conf put merge "$xml")
expectmatch "$ret" $? "0" ""
# expectfn "$datastore $conf put merge $xml" 0 ""
new "datastore $name get"
expectfn "$datastore $conf get /" 0 "^$db$"
expectfn "$datastore $conf get /" 0 "^$xmlxxx$"
new "datastore $name put all delete"
expectfn "$datastore $conf put remove <config/>" 0 ""
@ -88,10 +96,11 @@ run(){
expectfn "$datastore $conf get /" 0 "^<config/>$"
new "datastore $name put all create"
expectfn "$datastore $conf put create $db" 0 ""
ret=$($datastore $conf put create "$xml")
expectmatch "$ret" $? "0" ""
new "datastore $name get"
expectfn "$datastore $conf get /" 0 "^$db$"
expectfn "$datastore $conf get /" 0 "^$xmlxxx$"
new "datastore $name put top create"
expectfn "$datastore $conf put create <config><x/></config>" 0 "" # error
@ -159,7 +168,6 @@ run(){
rm -rf $mydir
}
#run keyvalue # cant get the put to work
run text
rm -rf $dir

View file

@ -57,19 +57,21 @@ module $APPNAME{
}
}
EOF
new "start backend -s init -f $cfg -y $fyang"
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "cli enabled feature"
@ -94,7 +96,8 @@ new "netconf validate enabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf disabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><A>foo</A></config></edit-config></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>XML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?'
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><A>foo</A></config></edit-config></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>Validation failed</error-message></rpc-error></rpc-reply>]]>]]>$'
#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><A>foo</A></config></edit-config></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>XML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?'
# This test has been broken up into all different modules instead of one large
# reply since the modules change so often
@ -134,7 +137,7 @@ if [ -z "$match" ]; then
fi
new "netconf module ietf-netconf"
expect="module><name>ietf-netconf</name><revision>2011-06-01</revision><namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace><conformance-type>implement</conformance-type></module>"
expect="module><name>ietf-netconf</name><revision>2011-06-01</revision><namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace><feature>candidate</feature><feature>startup</feature><feature>validate</feature><feature>xpath</feature><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
@ -159,6 +162,10 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -106,21 +106,23 @@ cat <<EOF > $fyang
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "Set crypto to aes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><crypto>aes</crypto></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><crypto>aes</crypto></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf validate "
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -180,6 +182,10 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$"
new "cli validate"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -72,18 +72,19 @@ module example{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg -y $fyang"
# start new backend
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "leafref base config"
@ -142,6 +143,10 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$"
new "cli sender template"
expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -64,18 +64,20 @@ module $APPNAME{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg -y $fyang"
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "minmax: minimal"
@ -123,6 +125,10 @@ new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
fi # NYI
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -6,6 +6,7 @@
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
@ -45,30 +46,17 @@ module $APPNAME{
}
EOF
# The groups are slightly modified from RFC8341 A.1
# The rule-list is from A.2
RULES=$(cat <<EOF
<nacm>
<enable-nacm>false</enable-nacm>
<read-default>deny</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
<groups>
<group>
<name>admin</name>
<user-name>admin</user-name>
<user-name>adm1</user-name>
<user-name>olof</user-name>
</group>
<group>
<name>limited</name>
<user-name>wilma</user-name>
<user-name>bam-bam</user-name>
</group>
<group>
<name>guest</name>
<user-name>guest</user-name>
<user-name>guest@example.com</user-name>
</group>
</groups>
$NGROUPS
<rule-list>
<name>guest-acl</name>
<group>guest</group>
@ -78,7 +66,8 @@ RULES=$(cat <<EOF
<access-operations>*</access-operations>
<action>deny</action>
<comment>
Do not allow guests any access to any information.
Do not allow guests any access to the NETCONF
monitoring information.
</comment>
</rule>
</rule-list>
@ -106,36 +95,27 @@ RULES=$(cat <<EOF
</comment>
</rule>
</rule-list>
<rule-list>
<name>admin-acl</name>
<group>admin</group>
<rule>
<name>permit-all</name>
<module-name>*</module-name>
<access-operations>*</access-operations>
<action>permit</action>
<comment>
Allow the 'admin' group complete access to all operations and data.
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
<x>0</x>
EOF
)
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg -y $fyang"
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
@ -148,10 +128,10 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "restconf DELETE whole datastore"
expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" ""
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
new2 "auth get"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" 'null
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null
'
new "auth set authentication config"
@ -164,19 +144,19 @@ new2 "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (wrong passwd: access denied)"
expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (access)"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
'
#----------------Enable NACM
new "enable nacm"
expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" ""
new2 "admin get nacm"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
'
new2 "limited get nacm"
@ -184,20 +164,24 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x
'
new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} '
new "admin edit nacm"
expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" ""
new2 "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} '
new2 "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -7,6 +7,7 @@
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
@ -40,7 +41,12 @@ cat <<EOF > $fyang
module $APPNAME{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import ietf-routing {
description "For fib-route";
prefix rt;
}
container authentication {
description "Example code for enabling www basic auth and some example
users";
@ -82,23 +88,9 @@ cat <<EOF > $nacmfile
<read-default>deny</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
<groups>
<group>
<name>admin</name>
<user-name>admin</user-name>
<user-name>adm1</user-name>
</group>
<group>
<name>limited</name>
<user-name>wilma</user-name>
<user-name>bam-bam</user-name>
</group>
<group>
<name>guest</name>
<user-name>guest</user-name>
<user-name>guest@example.com</user-name>
</group>
</groups>
$NGROUPS
<rule-list>
<name>guest-acl</name>
<group>guest</group>
@ -136,34 +128,27 @@ cat <<EOF > $nacmfile
</comment>
</rule>
</rule-list>
<rule-list>
<name>admin-acl</name>
<group>admin</group>
<rule>
<name>permit-all</name>
<module-name>*</module-name>
<access-operations>*</access-operations>
<action>permit</action>
<comment>
Allow the 'admin' group complete access to all operations and data.
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
EOF
# kill old backend (if any)
new "kill old backend -zf $cfg -y $fyang"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
sleep 1
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend -zf $cfg -y $fyang"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
sleep 1
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
@ -175,27 +160,27 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "restconf DELETE whole datastore"
expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" ""
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
new2 "auth get"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
'
new "Set x to 0"
expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" ""
new2 "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (wrong passwd: access denied)"
expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (access)"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
'
new2 "admin get nacm"
expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
'
new2 "limited get nacm"
@ -203,19 +188,19 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x
'
new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} '
new "admin edit nacm"
expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" ""
new2 "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} '
new2 "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} '
new "cli show conf as admin"
expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
new "cli show conf as limited"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
@ -224,7 +209,7 @@ new "cli show conf as guest"
expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied"
new "cli rpc as admin"
expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang rpc ipv4" 0 "<next-hop-list>2.3.4.5</next-hop-list>"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "<next-hop-list>2.3.4.5</next-hop-list>"
new "cli rpc as limited"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny"
@ -235,6 +220,10 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

231
test/test_nacm_protocol.sh Executable file
View file

@ -0,0 +1,231 @@
#!/bin/bash
# Authentication and authorization and IETF NACM
# NACM protocol operation rules
# @see RFC 8341 A.1 and A.3 (and permit-all from A.2)
# Tests for three protocol operation rules (all apply to module ietf-netconf)
# deny-kill-session: This rule prevents the "limited" group or the
# "guest" group from invoking the NETCONF <kill-session> protocol
# operation.
# deny-delete-config: This rule prevents the "limited" group or the
# "guest" group from invoking the NETCONF <delete-config> protocol
# operation.
# permit-edit-config: This rule allows the "limited" group to invoke
# the NETCONF <edit-config> protocol operation. This rule will have
# no real effect unless the "exec-default" leaf is set to "deny".
#
# From RFC8040, I conclude that commit/discard should be done automatically
# BY THE SYSTEM
# Otherwise, if the device supports :candidate, all edits to
# configuration nodes in {+restconf}/data are performed in the
# candidate configuration datastore. The candidate MUST be
# automatically committed to running immediately after each successful
# edit.
# Which means that restconf -X DELETE /data translates to edit-config + commit
# WHICH IS allowed.
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
fyangerr=$dir/err.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
</config>
EOF
cat <<EOF > $fyang
module $APPNAME{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import ietf-netconf-acm {
prefix nacm;
}
leaf x{
type int32;
description "something to edit";
}
}
EOF
# The groups are slightly modified from RFC8341 A.1
# The rule-list is from A.2
RULES=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>false</enable-nacm>
<read-default>deny</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
$NGROUPS
<rule-list>
<name>guest-limited-acl</name>
<group>limited</group>
<group>guest</group>
<rule>
<name>deny-kill-session</name>
<module-name>ietf-netconf</module-name>
<rpc-name>kill-session</rpc-name>
<access-operations>exec</access-operations>
<action>deny</action>
<comment>
Do not allow the 'limited' group or the 'guest' group
to kill another session.
</comment>
</rule>
<rule>
<name>deny-delete-config</name>
<module-name>ietf-netconf</module-name>
<rpc-name>delete-config</rpc-name>
<access-operations>exec</access-operations>
<action>deny</action>
<comment>
Do not allow the 'limited' group or the 'guest' group
to delete any configurations.
</comment>
</rule>
</rule-list>
<rule-list>
<name>limited-acl</name>
<group>limited</group>
<rule>
<name>permit-edit-config</name>
<module-name>ietf-netconf</module-name>
<rpc-name>edit-config</rpc-name>
<access-operations>exec</access-operations>
<action>permit</action>
<comment>
Allow the 'limited' group to edit the configuration.
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
<x>0</x>
EOF
)
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" ""
new2 "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0}
'
# Rule 1: deny-kill-session
new "deny-kill-session: limited fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>access-denied</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new "deny-kill-session: guest fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>access-denied</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new "deny-kill-session: admin ok (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Rule 2: deny-delete-config
new "deny-delete-config: limited fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>access-denied</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new2 "deny-delete-config: guest fail (restconf)"
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} '
# In restconf delete-config is translated to edit-config which is permitted
new "deny-delete-config: limited fail (restconf) ok"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" ''
new2 "admin get nacm (should be null)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null
'
new "deny-delete-config: admin ok (restconf)"
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ''
# Here the whole config is gone so we need to start again
new "auth set authentication config (restart)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" ""
# Rule 3: permit-edit-config
new "permit-edit-config: limited ok restconf"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" ''
new2 "permit-edit-config: guest fail restconf"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
#rm -rf $dir # XXX

View file

@ -87,17 +87,21 @@ module example{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
# Bring your own backend
if [ $BE -ne 0 ]; then
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "netconf hello"
@ -222,6 +226,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><lock><target><candidate/
new "close-session"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><close-session/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# XXX NOTE that this does not actually kill a running session - and may even kill some random process,...
new "kill-session"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -238,10 +243,10 @@ new "netconf check empty startup"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
new "netconf rpc"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><rt:fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></rt:fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><fib-route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>]]>]]>' "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
new "netconf rpc without namespace"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><rt:fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></rt:fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
new "netconf rpc without namespace (iterate kludge should work)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><fib-route><routing-instance-name>ipv4</routing-instance-name><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>]]>]]>" "^<rpc-reply><route><address-family>ipv4</address-family><next-hop><next-hop-list>"
new "netconf empty rpc"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><ex:empty/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -249,6 +254,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><ex:empty/></rpc>]]>]]>"
new "netconf client-side rpc"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><ex:client-rpc><request>example</request></ex:client-rpc></rpc>]]>]]>" "^<rpc-reply><result>ok</result></rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -107,18 +107,19 @@ cat <<EOF > $dbdir/running_db
</config>
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg -y $fyang"
new "start backend"
# start new backend
sudo $clixon_backend -s running -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend"
sudo $clixon_backend -s running -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
# Check as file
@ -171,6 +172,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><can
new "verify list user order (as entered)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/y2\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><y2><k>c</k><a>bar</a></y2><y2><k>b</k><a>foo</a></y2><y2><k>a</k><a>fie</a></y2></data></rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -12,7 +12,7 @@ elif [ $# = 2 ]; then
req=$2
else
echo "Usage: $0 [<number> [<requests>]]"
exit 1
exit 1 # Scaling
fi
APPNAME=example
# include err() and new() functions and creates $dir
@ -58,18 +58,20 @@ cat <<EOF > $cfg
</config>
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg" -y $fyang
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
@ -103,10 +105,10 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc
rm $fconfig
new "netconf commit large config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit><source><candidate/></source></commit></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit large config again"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit><source><candidate/></source></commit></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf add small (1 entry) config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><x><y><a>x</a><b>y</b></y></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
@ -151,7 +153,7 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^<rpc
rm $fconfig
new "netconf commit large leaf-list config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit><source><candidate/></source></commit></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf add $req small leaf-list config"
time -p for (( i=0; i<$req; i++ )); do
@ -163,7 +165,7 @@ new "netconf add small leaf-list config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><x><c>x</c></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit small leaf-list config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit><source><candidate/></source></commit></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get large leaf-list config"
expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><x><c>0</c><c>1</c>"
@ -171,6 +173,10 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -86,23 +86,25 @@ EOF
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
state='{"state": {"op": "42"}}'
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data &
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data &
sleep $RCWAIT
@ -128,7 +130,7 @@ expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations)
expect="<operations><example:empty/><example:input/><example:output/><example:client-rpc/><ietf-routing:fib-route/><ietf-routing:route-count/></operations>"
expect='<operations><empty xmlns="urn:example:clixon"/><input xmlns="urn:example:clixon"/><output xmlns="urn:example:clixon"/><client-rpc xmlns="urn:example:clixon"/><fib-route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"/><route-count xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"/></operations>'
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
@ -157,7 +159,7 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf empty rpc"
expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" ""
expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" ""
new2 "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}}
@ -319,6 +321,10 @@ fi
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -46,16 +46,19 @@ module example{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
@ -140,6 +143,10 @@ expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http:/
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -102,16 +102,19 @@ cat <<EOF > $fyang
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
@ -275,6 +278,10 @@ sleep 5
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -192,16 +192,19 @@ module example{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
fi
new "cli set transitive string"
@ -300,6 +303,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><can
new "cli bits validate"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang validate" 0 "^$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

119
test/test_union.sh Executable file
View file

@ -0,0 +1,119 @@
#!/bin/bash
# Advanced union types and generated code
# and enum w values
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
fyang=$dir/type.yang
fyang2=$dir/example2.yang
fyang3=$dir/example3.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
</config>
EOF
# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang
cat <<EOF > $fyang3
module example3{
prefix ex3;
namespace "urn:example:example3";
typedef u{
type union {
type int32{
range "4..44";
}
type enumeration {
enum "unbounded";
}
}
}
typedef t{
type string;
}
}
EOF
cat <<EOF > $fyang2
module example2{
import example3 { prefix ex3; }
namespace "urn:example:example2";
prefix ex2;
grouping gr2 {
leaf talle{
type ex3:t;
}
leaf ulle{
type ex3:u;
}
}
}
EOF
cat <<EOF > $fyang
module example{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import example2 { prefix ex2; }
container c{
description "transitive type- exists in ex3";
uses ex2:gr2;
}
}
EOF
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
fi
new "cli set transitive string"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle x" 0 "^$"
new "cli set transitive union"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 33" 0 "^$"
new "cli set transitive union error"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle kalle" 255 ""
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

View file

@ -90,18 +90,19 @@ module $APPNAME{
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "test params: -f $cfg -y $fyang"
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "when: add static route"
@ -140,6 +141,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><can
new "must: eth validate fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>An Ethernet MTU must be 1500</error-message></rpc-error></rpc-reply>]]>]]>"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -13,7 +13,6 @@ fyangerr=$dir/err.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>$APPNAME</CLICON_YANG_MODULE_MAIN>
@ -141,18 +140,20 @@ module $APPNAME{
ex:not-defined ARGUMENT;
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg -y $fyang"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "cli defined extension"
@ -278,6 +279,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candid
new "netconf submodule discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`

View file

@ -69,28 +69,33 @@ cat <<EOF > $cfg
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
</config>
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
# start new backend
sudo $clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then
err
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "1. Set newex"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><newex>str</newex></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
new "Set oldex should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><oldex>str</oldex></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>XML'
new "Set oldex should fail (since oldex is in old revision and only the new is loaded)"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><oldex>str</oldex></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>Validation failed</error-message></rpc-error></rpc-reply>]]>]]>$'
new "Set other should fail"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><other>str</other></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>XML'
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><other>str</other></config></edit-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>Validation failed</error-message></rpc-error></rpc-reply>]]>]]>$'
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill

141
test/test_yang_namespace.sh Executable file
View file

@ -0,0 +1,141 @@
#!/bin/bash
# Yang specifics: multi-keys and empty type
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
fyang=$dir/example.yang
fyang2=$dir/example2.yang
# <CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
</config>
EOF
# For testing namespaces -
# x.y is different type. Here it is string whereas in fyang it is list.
#
cat <<EOF > $fyang2
module example2{
yang-version 1.1;
prefix ex2;
namespace "urn:example:clixon2";
container x {
leaf y {
type uint32;
}
}
}
EOF
cat <<EOF > $fyang
module example{
yang-version 1.1;
prefix ex;
namespace "urn:example:clixon";
import ietf-routing {
description "defines fib-route";
prefix rt;
}
leaf x{
type int32;
}
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.
This is a clixon implementation detail: some rpc:s
are better processed by the client for API or perf reasons";
input {
leaf request {
type string;
}
}
output {
leaf result{
type string;
}
}
}
}
EOF
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg"
# start new backend
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "netconf xmlns module ex"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon">42</x></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get config XXX xmlfn in return"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><x>42</x></data></rpc-reply>]]>]]>$"
new "netconf xmlns module ex2"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><x xmlns="urn:example:clixon2"><y>99</y></x></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ok/></rpc-reply>]]>]]>$'
new "netconf get config XXX xmlns"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><x>42</x><x><y>99</y></x></data></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf xmlns:ex"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ex="urn:example:clixon"><edit-config><target><candidate/></target><config><ex:x>4422</ex:x></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ex="urn:example:clixon"><ok/></rpc-reply>]]>]]>$'
new "netconf get config XXX xmlns:ex"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><x>4422</x></data></rpc-reply>]]>]]>$"
new "netconf xmlns:ex2"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ex2="urn:example:clixon2"><edit-config><target><candidate/></target><config><ex2:x><ex2:y>9999</ex2:y></ex2:x></config></edit-config></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ex2="urn:example:clixon2"><ok/></rpc-reply>]]>]]>$'
new "netconf get config XXX xmlns:ex2"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><x>4422</x><x><y>9999</y></x></data></rpc-reply>]]>]]>$"
# rpc
if [ $BE -ne 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=`pgrep -u root -f clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
sudo pkill -u root -f clixon_backend
rm -rf $dir

View file

@ -123,7 +123,7 @@ module clixon-config {
"Supported features as used by YANG feature/if-feature
value is: <module>:<feature>, where <module> and <feature>
are either names, or the special character '*'.
*:an* means enable all features
*:* means enable all features
<module>:* means enable all features in the specified module
*:<feature> means enable the specific feature in all modules";
type string;
@ -150,7 +150,7 @@ module clixon-config {
leaf CLICON_YANG_MAIN_DIR {
type string;
description
"If given, load all modules in this directory.
"If given, load all modules in this directory (all .yang files)
See also CLICON_YANG_DIR which specifies a path of dirs";
}
leaf CLICON_YANG_MODULE_MAIN {
@ -353,6 +353,15 @@ module clixon-config {
Only works for Yang specified XML.
If not set, all lists accessed via linear search.";
}
leaf CLICON_XML_NS_ITERATE {
type boolean;
default true;
description
"If set, iterate through modules to find the matching datanode
or rpc if no xmlns attribute specifies namespace.
This is loose semantics of finding namespaces.
And it is wrong, but is the way Clixon originally was written.";
}
leaf CLICON_USE_STARTUP_CONFIG {
type int32;
default 0;
@ -450,4 +459,12 @@ module clixon-config {
}
}
rpc debug {
description "Set debug level of backend.";
input {
leaf level {
type uint32;
}
}
}
}