Merge branch 'develop' into nacm

This commit is contained in:
Olof hagsand 2019-01-12 12:29:34 +01:00
commit 9a7ce8e06d
194 changed files with 5941 additions and 3452 deletions

5
.gitignore vendored
View file

@ -14,10 +14,9 @@ lib/Makefile
lib/*/Makefile lib/*/Makefile
autom4te.cache/ autom4te.cache/
clixon.conf.cpp
clixon.mk
config.log config.log
config.status config.status
TAGS
apps/backend/clixon_backend apps/backend/clixon_backend
apps/backend/test apps/backend/test
@ -45,5 +44,5 @@ build-root/*.tar.xz
build-root/*.rpm build-root/*.rpm
build-root/rpmbuild build-root/rpmbuild
test/public test/site.sh
doc/html doc/html

View file

@ -1,47 +1,105 @@
# Clixon Changelog # Clixon Changelog
## 3.9.0 (Preliminary Target: 31 December 2018) ## 3.9.0 (Preliminary Target: Mid-January 2019)
### Planned new features ### Planned new features
* [Roadmap](ROADMAP.md) (Uncommitted and unprioritized) * [Roadmap](ROADMAP.md)
### Major New features ### Major New features
* Correct XML namespace handling
* NACM extension (RFC8341) * XML multiple modules was based on non-strict 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) as well as strict Netconf and Restconf namespace handling, which causes problems with overlapping names and false positives, and most importantly, with standard conformance.
* NACM module support (RFC8341 A1+A2) * There are still the following non-strict namespace handling:
* Recovery user "_nacm_recovery" added. * Everything in ietf-netconf base syntax with namespace `urn:ietf:params:xml:ns:netconf:base:1.0` is default and need not be explicitly given
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. * edit-config xpath select statement does not support namespaces
* Example user changed adm1 to andy to comply with RFC8341 example * notifications do not support namespaces.
* Below see netconf old (but wrong) netconf RPC:
```
<rpc>
<my-own-method/>
</rpc>
<rpc-reply>
<route>
<address-family>ipv4</address-family>
</route>
</rpc-reply>
```
This is the currently correct Netconf RPC:
```
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # xmlns may be ommitted
<my-own-method xmlns="urn:example:my-own">
</rpc>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
<address-family>ipv4</address-family>
</route>
</rpc-reply>
```
* Another example for restconf rpc with new correct syntax. Note that while Netconf uses xmlns attribute syntax, Restconf uses module name prefix. First the request:
```
POST http://localhost/restconf/operations/example:example)
Content-Type: application/yang-data+json
{
"example:input":{
"x":0
}
}
```
then the reply:
```
HTTP/1.1 200 OK
{
"example:output": {
"x": "0",
"y": "42"
}
}
```
* To keep previous non-strict namespace handling (backwards compatible), set CLICON_XML_NS_STRICT to false.
* See https://github.com/clicon/clixon/issues/49
* Yang code upgrade (RFC7950) * Yang code upgrade (RFC7950)
* YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48)
* See https://github.com/clicon/clixon/issues/84 * See https://github.com/clicon/clixon/issues/84
* RPC method input parameters validated * RPC method input parameters validated
* see https://github.com/clicon/clixon/issues/47 * see https://github.com/clicon/clixon/issues/47
* Support of submodule, include and belongs-to. * Support of submodule, include and belongs-to.
* Openconfig yang specs parsed: https://github.com/openconfig/public * Parsing of standard yang files supported, such as:
* https://github.com/openconfig/public - except [https://github.com/clicon/clixon/issues/60].
* See [test/test_openconfig.sh]
* https://github.com/YangModels/yang - except vendor-specific specs
* See [test/test_yangmodels.sh]
* Improved "unknown" handling * Improved "unknown" handling
* More precise Yang validation and better error messages
* Example: adding bad-, missing-, or unknown-element error messages, instead of operation-failed.
* Validation of mandatory choice and recursive mandatory containers
* Yang load file configure options changed * Yang load file configure options changed
* `CLICON_YANG_DIR` is changed from a single directory to a path of directories * `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 * 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_FILE Provides a filename with a single module filename.
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
* Correct XML namespace handling * NACM extension (RFC8341)
* 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: * NACM module support (RFC8341 A1+A2)
``` * Recovery user "_nacm_recovery" added.
<rpc><my-own-method></rpc> # Wrong but accepted * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> # Correct * Example user changed adm1 to andy to comply with RFC8341 example
<my-own-method xmlns="http://example.net/me/my-own/1.0"> * Added -o "<option>=<value>" command-line option to all programs: backend, cli, netconf, restconf.
</rpc> * Any config option from file can be overrided by giving them on command-line.
```
* 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) ### API changes on existing features (you may need to change your code)
* Date-and-time type now properly uses ISO 8601 UTC timezone designators.
* Eg 2008-09-21T18:57:21.003456 is changed to 2008-09-21T18:57:21.003456Z
* Renamed yang file `ietf-netconf-notification@2008-07-01.yang` to `clixon-rfc5277`.
* Fixed validation problems, see [https://github.com/clicon/clixon/issues/62]
* Name confusion, the file is manually constructed from the rfc.
* Changed prefix to `ncevent`
* Stricter YANG choice validation leads to enforcement of structures like: `choice c{ mandatory true; leaf x` statements. `x` was not previously enforced.
* Many hand-crafted validation messages have been removed and replaced with generic validations, which may lead to changed rpc-error messages.
* CLICON_XML_SORT option (in clixon-config.yang) has been removed and set to true permanently. Unsorted XML lists leads to slower performance and old obsolete code can be removed.
* Strict namespace setting can be a problem when upgrading existing database files, such as startup-db or persistent running-db, or any other saved XML file.
* Removed `delete-config` support for candidate db since it is not supported in RFC6241.
* Switched the order of `error-type` and `error-tag` in all netconf and restconf error messages to comply to RFC order.
* Yang parser is stricter (see above) which may break parsing of existing yang specs. * Yang parser is stricter (see above) which may break parsing of existing yang specs.
* XML namespace handling is corrected (see above) * XML namespace handling is corrected (see above)
* For backward compatibility set config option CLICON_XML_NS_ITERATE * For backward compatibility set config option CLICON_XML_NS_LOOSE
* Yang parser functions have changed signatures. Please check the source if you call these functions. * 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. * 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 * Change all @datamodel:tree to @datamodel in all CLI specification files
@ -49,6 +107,13 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes ### Minor changes
* Cligen uses posix regex while yang uses XSD. It differs in some aspects. A translator function has been added for `\d` -> `[0-9]` translation, there may be more.
* Added new clixon-lib yang module for internal netconf protocol. Currently only extends the standard with a debug RPC.
* Added three-valued return values for several validate functions where -1 is fatal error, 0 is validation failed and 1 is validation OK.
* This includes: `xmldb_put`, `xml_yang_validate_all`, `xml_yang_validate_add`, `xml_yang_validate_rpc`, `api_path2xml`, `api_path2xpath`
* Added new xml functions for specific types: `xml_child_nr_notype`, `xml_child_nr_notype`, `xml_child_i_type`, `xml_find_type`.
* Added example_rpc RPC to example backend
* Renamed xml_namespace[_set]() to xml_prefix[_set]()
* Changed all make tags --> make TAGS * Changed all make tags --> make TAGS
* Keyvalue datastore removed (it has been disabled since 3.3.3) * Keyvalue datastore removed (it has been disabled since 3.3.3)
* Removed return value ymodp from yang parse functions (eg yang_parse()). * Removed return value ymodp from yang parse functions (eg yang_parse()).
@ -60,7 +125,14 @@
* <!DOCTYPE (ie DTD) is not supported. * <!DOCTYPE (ie DTD) is not supported.
### Corrected Bugs ### Corrected Bugs
* [ietf-netconf-notification@2008-07-01.yang validation problem #62](https://github.com/clicon/clixon/issues/62)
* Ignore CR(\r) in yang files for DOS files
* Keyword "min" (not only "max") can be used in built-in types "range" and "length" statements.
* Support for empty yang string added, eg `default "";`
* Removed CLI generation for yang notifications (and other non-data yang nodes)
* Some restconf error messages contained "rpc-reply" or "rpc-error" which have now been removed.
* getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58) * getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58)
* Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47)
### Known issues ### Known issues
* debug rpc added in example application (should be in clixon-config). * debug rpc added in example application (should be in clixon-config).
@ -265,7 +337,7 @@ translate {
### Known issues ### Known issues
* Namespace name relabeling is not supported. * Namespace name relabeling is not supported.
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlfns is not supported, such as: * Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as:
``` ```
<crypto xmlns:x="urn:example:des">x:des3</crypto> <crypto xmlns:x="urn:example:des">x:des3</crypto>
``` ```

View file

@ -97,6 +97,9 @@ EOF
## New release ## New release
What to think about when doing a new release. What to think about when doing a new release.
* valgrind for memory leaks
* New clixon-config.yang revision?
Tagging:
* git merge --no-ff develop * git merge --no-ff develop
* change CLIXON_VERSION in configure.ac * change CLIXON_VERSION in configure.ac
* git tag -a <version" * git tag -a <version"

View file

@ -1,4 +1,4 @@
Copyright 2009-2018 Olof Hagsand and Benny Holmgren Copyright 2009-2019 Olof Hagsand and Benny Holmgren
CLIXON is dual license. CLIXON is dual license.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -105,6 +105,23 @@ The standards covered include:
Not supported: Not supported:
- !DOCTYPE (ie DTD) - !DOCTYPE (ie DTD)
Historically, Clixon has not until 3.9 made strict namespace
enforcing. For example, the following non-strict netconf was
previously accepted:
```
<rpc><my-own-method/></rpc>
```
In 3.9, the same statement should be, for example:
```
<rpc><my-own-method xmlns="urn:example:my-own"/></rpc>
```
Note that base netconf syntax is still not enforced but recommended:
```
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<my-own-method xmlns="urn:example:my-own"/>
</rpc>
```
Yang Yang
==== ====
YANG and XML is the heart of Clixon. Yang modules are used as a YANG and XML is the heart of Clixon. Yang modules are used as a
@ -125,8 +142,10 @@ However, the following YANG syntax modules are not implemented:
- belongs-to - belongs-to
Restrictions on Yang types are as follows: Restrictions on Yang types are as follows:
- The range statement does not support multiple values (RFC7895 sec 9.2.4) - The range statement for built-in integers does not support multiple values (RFC7950 9.2.4)
- The length statement for built-in strings does not support multiple values (RFC7950 9.4.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. (see https://github.com/clicon/clixon/issues/60) - 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)
- default values on leaf-lists (RFC7950 7.7.2)
Netconf Netconf
======= =======
@ -152,6 +171,9 @@ Clixon does not support the following netconf features:
- edit-config config-text - edit-config config-text
- edit-config operation - edit-config operation
Some other deviations from the RFC:
- edit-config xpath select statement does not support namespaces
Restconf Restconf
======== ========
Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to

View file

@ -1,27 +1,42 @@
# Clixon roadmap # Clixon roadmap
Not in prio order (yet) ## High prio
- NACM (RFC 8341)
- Module rules (done)
- Data node rules (read/create/delete/update/execute)
- Special handling of the initial startup transaction to avoid exit at startup
- Possibly - draft-wu-netconf-restconf-factory-restore-03
- Handle revisions to data model.
- Possibly draft-wang-netmod-module-revision-management-01
- XML [Namespace handling](https://github.com/clicon/clixon/issues/49)
- XML ## Medium prio:
- [Namespace handling](https://github.com/clicon/clixon/issues/49) - Input validation on custom RPCs/ (done)
- [Sanity checks](https://github.com/clicon/clixon/issues/47)
- Support for XML regex's.
- Currently Posix extended regular expressions
- Support a plugin callback that is invoked when copy-config is called.
- Preserve CLI command history across sessions. The up/down arrows
## Low prio:
- Provide a client library to access netconf APIs provided by system services.
- Netconf backend (Clixon acts as netconf controller)
- Support for restconf call-home (RFC 8071)
Not prioritized:
- Support for restconf PATCH method
- NETCONF - NETCONF
- Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53) - Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53)
- Netconf [framing](https://github.com/clicon/clixon/issues/50) - Netconf [framing](https://github.com/clicon/clixon/issues/50)
- [Sanity checks](https://github.com/clicon/clixon/issues/47)
- [Child ordering](https://github.com/clicon/clixon/issues/22) - [Child ordering](https://github.com/clicon/clixon/issues/22)
- Netconf backend (Clixon acts as netconf controller)
- Restconf - Restconf
- Query parameters - Query parameters
- NACM (RFC 8341) is somewhat limited
- Extend with data node access (read/create/delete/update/execute)
- Streams (netconf and restconf) - Streams (netconf and restconf)
- Extend native stream mode with external persistent timeseries database, eg influxdb. - Extend native stream mode with external persistent timeseries database, eg influxdb.
- Jenkins CI/CD and webhooks - Jenkins CI/CD and webhooks
- YANG - YANG
- [Cardinality](https://github.com/clicon/clixon/issues/48)
- RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39) - RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39)
- Factory default Setting - draft-wu-netconf-restconf-factory-restore-03 - Deviation, min/max-elements, action, unique
- Deviation, belongs-to, min/max-elements, action, unique
- Containers - Containers
- [Docker improvements](https://github.com/clicon/clixon/issues/44) - [Docker improvements](https://github.com/clicon/clixon/issues/44)
- Kubernetes Helm chart definition - Kubernetes Helm chart definition

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -292,7 +292,7 @@ client_get_streams(clicon_handle h,
* @param[in,out] xret Existing XML tree, merge x into this * @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal) * @retval -1 Error (fatal)
* @retval 0 OK * @retval 0 OK
* @retval 1 Statedata callback failed * @retval 1 Statedata callback failed (clicon_err called)
*/ */
static int static int
client_statedata(clicon_handle h, client_statedata(clicon_handle h,
@ -309,14 +309,14 @@ client_statedata(clicon_handle h,
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
} }
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
(retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) if ((retval = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) != 0)
goto done; goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040"))
(retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
goto done; goto done;
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") && if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
(retval = yang_modules_state_get(h, yspec, xret)) != 0) if ((retval = yang_modules_state_get(h, yspec, xret)) != 0)
goto done; goto done;
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0) if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
goto done; goto done;
@ -339,7 +339,7 @@ client_statedata(clicon_handle h,
/* reset flag */ /* reset flag */
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done; goto done;
retval = 0; retval = 0; /* OK */
done: done:
if (xvec) if (xvec)
free(xvec); free(xvec);
@ -363,7 +363,6 @@ from_client_get(clicon_handle h,
char *xpath = "/"; char *xpath = "/";
cxobj *xret = NULL; cxobj *xret = NULL;
int ret; int ret;
cbuf *cbx = NULL; /* Assist cbuf */
if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL)
@ -379,8 +378,12 @@ from_client_get(clicon_handle h,
clicon_err_reset(); clicon_err_reset();
if ((ret = client_statedata(h, xpath, &xret)) < 0) if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done; goto done;
if (ret == 0){ /* OK */ if (ret == 1){ /* Error from callback (error in xret) */
cprintf(cbret, "<rpc-reply>"); if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0)
goto done;
goto ok;
}
cprintf(cbret, "<rpc-reply>"); /* OK */
if (xret==NULL) if (xret==NULL)
cprintf(cbret, "<data/>"); cprintf(cbret, "<data/>");
else{ else{
@ -390,22 +393,9 @@ from_client_get(clicon_handle h,
goto done; goto done;
} }
cprintf(cbret, "</rpc-reply>"); cprintf(cbret, "</rpc-reply>");
}
else { /* 1 Error from callback */
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cbx, "Internal error:%s", clicon_err_reason);
if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0)
goto done;
clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe));
}
ok: ok:
retval = 0; retval = 0;
done: done:
if (cbx)
cbuf_free(cbx);
if (xret) if (xret)
xml_free(xret); xml_free(xret);
return retval; return retval;
@ -433,14 +423,16 @@ from_client_edit_config(clicon_handle h,
int non_config = 0; int non_config = 0;
yang_spec *yspec; yang_spec *yspec;
cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbx = NULL; /* Assist cbuf */
int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9"); clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done; goto done;
} }
if ((target = netconf_db_find(xn, "target")) == NULL){ if ((target = netconf_db_find(xn, "target")) == NULL){
clicon_err(OE_XML, 0, "db not found"); if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done; goto done;
goto ok;
} }
if ((cbx = cbuf_new()) == NULL){ if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
@ -468,12 +460,12 @@ from_client_edit_config(clicon_handle h,
} }
} }
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) if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
else{ else{
/* <config> yang spec may be set to anyxml by ingress yang check,...*/ /* <config> yang spec may be set to anyxmly by ingress yang check,...*/
if (xml_spec(xc) != NULL) if (xml_spec(xc) != NULL)
xml_spec_set(xc, NULL); xml_spec_set(xc, NULL);
/* Populate XML with Yang spec (why not do this in parser?) /* Populate XML with Yang spec (why not do this in parser?)
@ -491,24 +483,27 @@ from_client_edit_config(clicon_handle h,
/* Cant do this earlier since we dont have a yang spec to /* Cant do this earlier since we dont have a yang spec to
* the upper part of the tree, until we get the "config" tree. * the upper part of the tree, until we get the "config" tree.
*/ */
if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
goto done; goto done;
if (xmldb_put(h, target, operation, xc, cbret) < 0){ if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){
clicon_debug(1, "%s ERROR PUT", __FUNCTION__); clicon_debug(1, "%s ERROR PUT", __FUNCTION__);
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
goto done; goto done;
goto ok; goto ok;
} }
if (ret == 0)
goto ok;
} }
ok: assert(cbuf_len(cbret) == 0);
if (!cbuf_len(cbret))
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0; retval = 0;
done: done:
if (cbx) if (cbx)
cbuf_free(cbx); cbuf_free(cbx);
clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret));
return retval; return retval;
} /* from_client_edit_config */ } /* from_client_edit_config */
/*! Internal message: Lock database /*! Internal message: Lock database
@ -530,7 +525,7 @@ from_client_lock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){ if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -589,7 +584,7 @@ from_client_unlock(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbx = NULL; /* Assist cbuf */
if ((db = netconf_db_find(xe, "target")) == NULL){ if ((db = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -651,7 +646,7 @@ from_client_kill_session(clicon_handle h,
if ((x = xml_find(xe, "session-id")) == NULL || if ((x = xml_find(xe, "session-id")) == NULL ||
(str = xml_find_value(x, "body")) == NULL){ (str = xml_find_value(x, "body")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>session-id</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -709,7 +704,7 @@ from_client_copy_config(clicon_handle h,
cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbx = NULL; /* Assist cbuf */
if ((source = netconf_db_find(xe, "source")) == NULL){ if ((source = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>source</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -724,7 +719,7 @@ from_client_copy_config(clicon_handle h,
goto ok; goto ok;
} }
if ((target = netconf_db_find(xe, "target")) == NULL){ if ((target = netconf_db_find(xe, "target")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -777,7 +772,7 @@ from_client_delete_config(clicon_handle h,
if ((target = netconf_db_find(xe, "target")) == NULL|| if ((target = netconf_db_find(xe, "target")) == NULL||
strcmp(target, "running")==0){ strcmp(target, "running")==0){
if (netconf_missing_element(cbret, "protocol", "<bad-element>target</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -856,7 +851,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//stopTime")) != NULL){ if ((x = xpath_first(xe, "//stopTime")) != NULL){
if ((stoptime = xml_find_value(x, "body")) != NULL && if ((stoptime = xml_find_value(x, "body")) != NULL &&
str2time(stoptime, &stop) < 0){ str2time(stoptime, &stop) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>stopTime</bad-element>", "Expected timestamp") < 0) if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -864,7 +859,7 @@ from_client_create_subscription(clicon_handle h,
if ((x = xpath_first(xe, "//startTime")) != NULL){ if ((x = xpath_first(xe, "//startTime")) != NULL){
if ((starttime = xml_find_value(x, "body")) != NULL && if ((starttime = xml_find_value(x, "body")) != NULL &&
str2time(starttime, &start) < 0){ str2time(starttime, &start) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>startTime</bad-element>", "Expected timestamp") < 0) if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -925,7 +920,7 @@ from_client_debug(clicon_handle h,
char *valstr; char *valstr;
if ((valstr = xml_find_body(xe, "level")) == NULL){ if ((valstr = xml_find_body(xe, "level")) == NULL){
if (netconf_missing_element(cbret, "application", "<bad-element>level</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "application", "level", NULL) < 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -971,6 +966,7 @@ from_client_msg(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
pid = ce->ce_pid; pid = ce->ce_pid;
yspec = clicon_dbspec_yang(h);
/* Return netconf message. Should be filled in by the dispatch(sub) functions /* Return netconf message. Should be filled in by the dispatch(sub) functions
* as wither rpc-error or by positive response. * as wither rpc-error or by positive response.
*/ */
@ -978,28 +974,26 @@ from_client_msg(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
if (clicon_msg_decode(msg, &xt) < 0){ if (clicon_msg_decode(msg, yspec, &xt) < 0){
if (netconf_malformed_message(cbret, "XML parse error")< 0) if (netconf_malformed_message(cbret, "XML parse error")< 0)
goto done; goto done;
goto reply; 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 ((x = xpath_first(xt, "/rpc")) == NULL){
if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) if (netconf_malformed_message(cbret, "rpc keyword expected")< 0)
goto done; goto done;
goto reply; goto reply;
} }
/* Populate incoming XML tree with yang */ /* Populate incoming XML tree with yang -
* should really have been dealt with by decode above
* maybe not necessary since it should be */
if (xml_spec_populate_rpc(h, x, yspec) < 0) if (xml_spec_populate_rpc(h, x, yspec) < 0)
goto done; goto done;
if ((ret = xml_yang_validate_rpc(x)) < 0) if ((ret = xml_yang_validate_rpc(x, cbret)) < 0)
goto done;
if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0)
goto done; goto done;
if (ret == 0)
goto reply; goto reply;
}
xe = NULL; xe = NULL;
username = xml_find_value(x, "username"); username = xml_find_value(x, "username");
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
@ -1059,7 +1053,7 @@ from_client_msg(clicon_handle h,
} }
else if (strcmp(rpc, "validate") == 0){ else if (strcmp(rpc, "validate") == 0){
if ((db = netconf_db_find(xe, "source")) == NULL){ if ((db = netconf_db_find(xe, "source")) == NULL){
if (netconf_missing_element(cbret, "protocol", "<bad-element>source</bad-element>", NULL) < 0) if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0)
goto done; goto done;
goto reply; goto reply;
} }

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -81,78 +81,108 @@
* are if code comes via XML/NETCONF. * are if code comes via XML/NETCONF.
* @param[in] yspec Yang spec * @param[in] yspec Yang spec
* @param[in] td Transaction data * @param[in] td Transaction data
* @param[out] cbret Cligen buffer containing netconf error (if retval == 0)
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/ */
static int static int
generic_validate(yang_spec *yspec, generic_validate(yang_spec *yspec,
transaction_data_t *td) transaction_data_t *td,
cbuf *cbret)
{ {
int retval = -1; int retval = -1;
cxobj *x1; cxobj *x1;
cxobj *x2; cxobj *x2;
yang_stmt *ys; yang_stmt *ys;
int i; int i;
int ret;
/* All entries */ /* All entries */
if (xml_apply(td->td_target, CX_ELMNT, if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0)
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
/* changed entries */ /* changed entries */
for (i=0; i<td->td_clen; i++){ for (i=0; i<td->td_clen; i++){
x1 = td->td_scvec[i]; /* source changed */ x1 = td->td_scvec[i]; /* source changed */
x2 = td->td_tcvec[i]; /* target changed */ x2 = td->td_tcvec[i]; /* target changed */
if (xml_yang_validate_add(x2, NULL) < 0) /* Should this be recursive? */
if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
} }
/* deleted entries */ /* deleted entries */
for (i=0; i<td->td_dlen; i++){ for (i=0; i<td->td_dlen; i++){
x1 = td->td_dvec[i]; x1 = td->td_dvec[i];
ys = xml_spec(x1); ys = xml_spec(x1);
if (ys && yang_mandatory(ys)){ if (ys && yang_mandatory(ys) && yang_config(ys)==0){
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Missing mandatory variable") < 0)
xml_name(x1));
goto done; goto done;
goto fail;
} }
} }
/* added entries */ /* added entries */
for (i=0; i<td->td_alen; i++){ for (i=0; i<td->td_alen; i++){
x2 = td->td_avec[i]; x2 = td->td_avec[i];
if (xml_apply0(x2, CX_ELMNT, if ((ret = xml_yang_validate_add(x2, cbret)) < 0)
(xml_applyfn_t*)xml_yang_validate_add, NULL) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
} }
retval = 0; // ok:
retval = 1;
done: done:
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Common code of candidate_validate and candidate_commit /*! Common code of candidate_validate and candidate_commit
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state * @param[in] candidate The candidate database. The wanted backend state
* @retval 0 OK * @retval -1 Error - or validation failed (but cbret not set)
* @retval -1 Fatal error or validation fail * @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail * @note Need to differentiate between error and validation fail
* (only done for generic_validate)
*/ */
static int static int
validate_common(clicon_handle h, validate_common(clicon_handle h,
char *candidate, char *candidate,
transaction_data_t *td) transaction_data_t *td,
cbuf *cbret)
{ {
int retval = -1; int retval = -1;
yang_spec *yspec; yang_spec *yspec;
int i; int i;
cxobj *xn; cxobj *xn;
int ret;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
} }
/* 2. Parse xml trees */ /* 2. Parse xml trees
* This is the state we are going from */
if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0) if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0)
goto done; goto done;
/* This is the state we are going to */
if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0)
goto done; goto done;
/* Validate the target state. It is not completely clear this should be done
* here. It is being made in generic_validate below.
* But xml_diff requires some basic validation, at least check that yang-specs
* have been assigned
*/
if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* 3. Compute differences */ /* 3. Compute differences */
if (xml_diff(yspec, if (xml_diff(yspec,
td->td_src, td->td_src,
@ -193,9 +223,12 @@ validate_common(clicon_handle h,
if (plugin_transaction_begin(h, td) < 0) if (plugin_transaction_begin(h, td) < 0)
goto done; goto done;
/* 5. Make generic validation on all new or changed data. */ /* 5. Make generic validation on all new or changed data.
if (generic_validate(yspec, td) < 0) Note this is only call that uses 3-values */
if ((ret = generic_validate(yspec, td, cbret)) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
/* 6. Call plugin transaction validate callbacks */ /* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0) if (plugin_transaction_validate(h, td) < 0)
@ -204,9 +237,12 @@ validate_common(clicon_handle h,
/* 7. Call plugin transaction complete callbacks */ /* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0) if (plugin_transaction_complete(h, td) < 0)
goto done; goto done;
retval = 0; retval = 1;
done: done:
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Do a diff between candidate and running, then start a commit transaction /*! Do a diff between candidate and running, then start a commit transaction
@ -216,57 +252,69 @@ validate_common(clicon_handle h,
* do something more drastic? * do something more drastic?
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] candidate A candidate database, not necessarily "candidate" * @param[in] candidate A candidate database, not necessarily "candidate"
* @retval 0 OK * @retval -1 Error - or validation failed
* @retval -1 Fatal error or validation fail * @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
* @note Need to differentiate between error and validation fail * @note Need to differentiate between error and validation fail
* (only done for validate_common)
*/ */
int int
candidate_commit(clicon_handle h, candidate_commit(clicon_handle h,
char *candidate) char *candidate,
cbuf *cbret)
{ {
int retval = -1; int retval = -1;
transaction_data_t *td = NULL; transaction_data_t *td = NULL;
int ret;
/* 1. Start transaction */ /* 1. Start transaction */
if ((td = transaction_new()) == NULL) if ((td = transaction_new()) == NULL)
goto done; goto done;
/* Common steps (with validate) */ /* Common steps (with validate). Load candidate and running and compute diffs
if (validate_common(h, candidate, td) < 0) * Note this is only call that uses 3-values
*/
if ((ret = validate_common(h, candidate, td, cbret)) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
/* 7. Call plugin transaction commit callbacks */ /* 7. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0) if (plugin_transaction_commit(h, td) < 0)
goto done; goto done;
/* Optionally write (potentially modified) tree back to candidate */ /* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, cbret)) < 0)
goto done; goto done;
if (ret == 0)
goto fail;
}
/* 8. Success: Copy candidate to running /* 8. Success: Copy candidate to running
*/ */
if (xmldb_copy(h, candidate, "running") < 0) if (xmldb_copy(h, candidate, "running") < 0)
goto done; goto done;
/* 9. Call plugin transaction end callbacks */ /* 9. Call plugin transaction end callbacks */
plugin_transaction_end(h, td); plugin_transaction_end(h, td);
/* 8. Copy running back to candidate in case end functions updated running */ /* 8. Copy running back to candidate in case end functions updated running */
if (xmldb_copy(h, "running", candidate) < 0){ if (xmldb_copy(h, "running", candidate) < 0){
/* ignore errors or signal major setback ? */ /* ignore errors or signal major setback ? */
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue"); clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done; goto done;
} }
retval = 0; retval = 1;
done: done:
/* In case of failure, call plugin transaction termination callbacks */ /* In case of failure (or error), call plugin transaction termination callbacks */
if (retval < 0 && td) if (retval < 1 && td)
plugin_transaction_abort(h, td); plugin_transaction_abort(h, td);
if (td) if (td)
transaction_free(td); transaction_free(td);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Commit changes from candidate to running /*! Commit changes from candidate to running
@ -283,6 +331,7 @@ from_client_commit(clicon_handle h,
int retval = -1; int retval = -1;
int piddb; int piddb;
cbuf *cbx = NULL; /* Assist cbuf */ cbuf *cbx = NULL; /* Assist cbuf */
int ret;
/* Check if target locked by other client */ /* Check if target locked by other client */
piddb = xmldb_islocked(h, "running"); piddb = xmldb_islocked(h, "running");
@ -296,9 +345,10 @@ from_client_commit(clicon_handle h,
goto done; goto done;
goto ok; goto ok;
} }
if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */ if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */
clicon_debug(1, "Commit candidate failed"); clicon_debug(1, "Commit candidate failed");
if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0) if (ret < 0)
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done; goto done;
goto ok; goto ok;
} }
@ -367,6 +417,7 @@ from_client_validate(clicon_handle h,
{ {
int retval = -1; int retval = -1;
transaction_data_t *td = NULL; transaction_data_t *td = NULL;
int ret;
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) if (netconf_invalid_value(cbret, "protocol", "No such database")< 0)
@ -379,17 +430,20 @@ from_client_validate(clicon_handle h,
if ((td = transaction_new()) == NULL) if ((td = transaction_new()) == NULL)
goto done; goto done;
/* Common steps (with commit) */ /* Common steps (with commit) */
if (validate_common(h, db, td) < 0){ if ((ret = validate_common(h, db, td, cbret)) < 1){
clicon_debug(1, "Validate %s failed", db); clicon_debug(1, "Validate %s failed", db);
/* XXX: candidate_validate should have proper error handling */ if (ret < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done; goto done;
}
goto ok; goto ok;
} }
/* Optionally write (potentially modified) tree back to candidate */ /* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0)
goto done; goto done;
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>"); cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok: ok:
retval = 0; retval = 0;

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -43,6 +43,6 @@
int from_client_validate(clicon_handle h, char *db, cbuf *cbret); int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, int pid, cbuf *cbret); int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret); int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
int candidate_commit(clicon_handle h, char *db); int candidate_commit(clicon_handle h, char *db, cbuf *cbret);
#endif /* _BACKEND_COMMIT_H_ */ #endif /* _BACKEND_COMMIT_H_ */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -73,7 +73,7 @@
#include "backend_handle.h" #include "backend_handle.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */ #define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:o:"
#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log" #define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
@ -154,7 +154,8 @@ usage(clicon_handle h,
"\t-g <group>\tClient membership required to this group (default: %s)\n" "\t-g <group>\tClient membership required to this group (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n" "\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-x <plugin>\tXMLDB plugin\n", "\t-x <plugin>\tXMLDB plugin\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0, argv0,
plgdir ? plgdir : "none", plgdir ? plgdir : "none",
confsock ? confsock : "none", confsock ? confsock : "none",
@ -176,11 +177,15 @@ db_reset(clicon_handle h,
} }
/*! Merge db1 into db2 without commit /*! Merge db1 into db2 without commit
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/ */
static int static int
db_merge(clicon_handle h, db_merge(clicon_handle h,
const char *db1, const char *db1,
const char *db2) const char *db2,
cbuf *cbret)
{ {
int retval = -1; int retval = -1;
cxobj *xt = NULL; cxobj *xt = NULL;
@ -189,9 +194,7 @@ db_merge(clicon_handle h,
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
goto done; goto done;
/* Merge xml into db2. Without commit */ /* Merge xml into db2. Without commit */
if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret);
goto done;
retval = 0;
done: done:
if (xt) if (xt)
xml_free(xt); xml_free(xt);
@ -266,7 +269,7 @@ nacm_load_external(clicon_handle h)
} }
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
if (yang_parse(h, NULL, "ietf-netconf-acm", NULL, yspec) < 0) if (yang_spec_parse_module(h, "ietf-netconf-acm", NULL, yspec) < 0)
goto done; goto done;
fd = fileno(f); fd = fileno(f);
/* Read configfile */ /* Read configfile */
@ -288,18 +291,22 @@ nacm_load_external(clicon_handle h)
} }
/*! Merge xml in filename into database /*! Merge xml in filename into database
* @retval -1 Error
* @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK
*/ */
static int static int
load_extraxml(clicon_handle h, load_extraxml(clicon_handle h,
char *filename, char *filename,
const char *db) const char *db,
cbuf *cbret)
{ {
int retval = -1; int retval = -1;
cxobj *xt = NULL; cxobj *xt = NULL;
int fd = -1; int fd = -1;
if (filename == NULL) if (filename == NULL)
return 0; return 1;
if ((fd = open(filename, O_RDONLY)) < 0){ if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, errno, "open(%s)", filename); clicon_err(OE_UNIX, errno, "open(%s)", filename);
goto done; goto done;
@ -310,9 +317,7 @@ load_extraxml(clicon_handle h,
if (xml_rootchild(xt, 0, &xt) < 0) if (xml_rootchild(xt, 0, &xt) < 0)
goto done; goto done;
/* Merge user reset state */ /* Merge user reset state */
if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret);
goto done;
retval = 0;
done: done:
if (fd != -1) if (fd != -1)
close(fd); close(fd);
@ -386,7 +391,12 @@ startup_mode_running(clicon_handle h,
char *extraxml_file) char *extraxml_file)
{ {
int retval = -1; int retval = -1;
cbuf *cbret = NULL;
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to candidate for later commit */ /* Stash original running to candidate for later commit */
if (xmldb_copy(h, "running", "candidate") < 0) if (xmldb_copy(h, "running", "candidate") < 0)
goto done; goto done;
@ -399,34 +409,46 @@ startup_mode_running(clicon_handle h,
/* Application may define extra xml in its reset function*/ /* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0) if (clixon_plugin_reset(h, "tmp") < 0)
goto done; goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */ /* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp") < 0) if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto done; goto fail;
/* Clear running db */ /* Clear running db */
if (db_reset(h, "running") < 0) if (db_reset(h, "running") < 0)
goto done; goto done;
/* Commit original running. Assume -1 is validate fail */ /* Commit original running. Assume -1 is validate fail */
if (candidate_commit(h, "candidate") < 0){ if (candidate_commit(h, "candidate", cbret) < 1)
goto fail;
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running", cbret) < 1)
goto fail;
retval = 0;
done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
fail:
/* (1) We cannot differentiate between fatal errors and validation /* (1) We cannot differentiate between fatal errors and validation
* failures * failures
* (2) If fatal error, we should exit * (2) If fatal error, we should exit
* (3) If validation fails we cannot continue. How could we? * (3) If validation fails we cannot continue. How could we?
* (4) Need to restore the running db since we destroyed it above * (4) Need to restore the running db since we destroyed it above
*/ */
clicon_log(LOG_NOTICE, "%s: Commit of saved running failed, exiting.", __FUNCTION__); if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of running failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
/* Reinstate original */ /* Reinstate original */
if (xmldb_copy(h, "candidate", "running") < 0) if (xmldb_copy(h, "candidate", "running") < 0)
goto done; goto done;
goto done; goto done;
}
/* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running") < 0)
goto done;
retval = 0;
done:
if (xmldb_delete(h, "tmp") < 0)
goto done;
return retval;
} }
/*! Clixon startup startup mode: Commit startup configuration into running state /*! Clixon startup startup mode: Commit startup configuration into running state
@ -455,7 +477,13 @@ startup_mode_startup(clicon_handle h,
char *extraxml_file) char *extraxml_file)
{ {
int retval = -1; int retval = -1;
cbuf *cbret = NULL;
/* Create return buffer for netconf xml errors */
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
/* Stash original running to backup */ /* Stash original running to backup */
if (xmldb_copy(h, "running", "backup") < 0) if (xmldb_copy(h, "running", "backup") < 0)
goto done; goto done;
@ -472,33 +500,46 @@ startup_mode_startup(clicon_handle h,
/* Application may define extra xml in its reset function*/ /* Application may define extra xml in its reset function*/
if (clixon_plugin_reset(h, "tmp") < 0) if (clixon_plugin_reset(h, "tmp") < 0)
goto done; goto done;
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = 0;
/* Get application extra xml from file */ /* Get application extra xml from file */
if (load_extraxml(h, extraxml_file, "tmp") < 0) if (load_extraxml(h, extraxml_file, "tmp", cbret) < 1)
goto done; goto fail;
/* Clear running db */ /* Clear running db */
if (db_reset(h, "running") < 0) if (db_reset(h, "running") < 0)
goto done; goto done;
/* Commit startup */ /* Commit startup */
if (candidate_commit(h, "startup") < 0){ /* diff */ if (candidate_commit(h, "startup", cbret) < 1) /* diff */
/* We cannot differentiate between fatal errors and validation goto fail;
* failures
* In both cases we copy back the original running and quit
*/
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting.", __FUNCTION__);
if (xmldb_copy(h, "backup", "running") < 0)
goto done;
goto done;
}
/* Merge user reset state and extra xml file (no commit) */ /* Merge user reset state and extra xml file (no commit) */
if (db_merge(h, "tmp", "running") < 0) if (db_merge(h, "tmp", "running", cbret) < 1)
goto done; goto fail;
retval = 0; retval = 0;
done: done:
/* XXX Kludge to low-level functions to search for xml in all yang modules */
_CLICON_XML_NS_STRICT = clicon_option_bool(h, "CLICON_XML_NS_STRICT");
if (cbret)
cbuf_free(cbret);
if (xmldb_delete(h, "backup") < 0) if (xmldb_delete(h, "backup") < 0)
goto done; goto done;
if (xmldb_delete(h, "tmp") < 0) if (xmldb_delete(h, "tmp") < 0)
goto done; goto done;
return retval; return retval;
fail:
/* We cannot differentiate between fatal errors and validation
* failures
* In both cases we copy back the original running and quit
*/
if (strlen(cbuf_get(cbret)))
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, cbuf_get(cbret));
else
clicon_log(LOG_NOTICE, "%s: Commit of startup failed, exiting: %s.",
__FUNCTION__, clicon_err_reason);
if (xmldb_copy(h, "backup", "running") < 0)
goto done;
goto done;
} }
int int
@ -523,7 +564,6 @@ main(int argc,
int sockfamily; int sockfamily;
char *xmldb_plugin; char *xmldb_plugin;
int xml_cache; int xml_cache;
int xml_pretty;
char *xml_format; char *xml_format;
char *nacm_mode; char *nacm_mode;
int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR; int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR;
@ -662,6 +702,15 @@ main(int argc,
clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg); clicon_option_str_set(h, "CLICON_XMLDB_PLUGIN", optarg);
break; break;
} }
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -766,6 +815,9 @@ main(int argc,
if ((str = clicon_yang_main_dir(h)) != NULL) if ((str = clicon_yang_main_dir(h)) != NULL)
if (yang_spec_load_dir(h, str, yspec) < 0) if (yang_spec_load_dir(h, str, yspec) < 0)
goto done; goto done;
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */ /* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0) if (yang_modules_init(h) < 0)
goto done; goto done;
@ -778,7 +830,7 @@ main(int argc,
goto done; goto done;
/* Load yang Netconf stream discovery */ /* Load yang Netconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done; goto done;
/* Set options: database dir and yangspec (could be hidden in connect?)*/ /* Set options: database dir and yangspec (could be hidden in connect?)*/
if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0) if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0)
@ -791,8 +843,7 @@ main(int argc,
if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0) if ((xml_format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) >= 0)
if (xmldb_setopt(h, "format", (void*)xml_format) < 0) if (xmldb_setopt(h, "format", (void*)xml_format) < 0)
goto done; goto done;
if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0)
goto done; goto done;
/* Startup mode needs to be defined, */ /* Startup mode needs to be defined, */
startup_mode = clicon_startup_mode(h); startup_mode = clicon_startup_mode(h);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -117,7 +117,7 @@ clixon_plugin_reset(clicon_handle h,
* @param[in,out] xtop State XML tree is merged with existing tree. * @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error * @retval -1 Error
* @retval 0 OK * @retval 0 OK
* @retval 1 Statedata callback failed * @retval 1 Statedata callback failed (xret set with netconf-error)
* @note xtop can be replaced * @note xtop can be replaced
*/ */
int int

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h,
if ((xtop = xml_new("config", NULL, NULL)) == NULL) if ((xtop = xml_new("config", NULL, NULL)) == NULL)
goto done; goto done;
xbot = xtop; xbot = xtop;
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1)
goto done; goto done;
if ((xa = xml_new("operation", xbot, NULL)) == NULL) if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done; goto done;
@ -293,7 +293,7 @@ cli_set(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
int retval = 1; int retval = -1;
if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0) if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0)
goto done; goto done;
@ -503,7 +503,7 @@ cli_start_shell(clicon_handle h,
{ {
char *cmd; char *cmd;
struct passwd *pw; struct passwd *pw;
int retval; int retval = -1;
char bcmd[128]; char bcmd[128];
cg_var *cv1 = cvec_i(vars, 1); cg_var *cv1 = cvec_i(vars, 1);
@ -512,42 +512,43 @@ cli_start_shell(clicon_handle h,
if ((pw = getpwuid(getuid())) == NULL){ if ((pw = getpwuid(getuid())) == NULL){
fprintf(stderr, "%s: getpwuid: %s\n", fprintf(stderr, "%s: getpwuid: %s\n",
__FUNCTION__, strerror(errno)); __FUNCTION__, strerror(errno));
return -1; goto done;
} }
if (chdir(pw->pw_dir) < 0){ if (chdir(pw->pw_dir) < 0){
fprintf(stderr, "%s: chdir(%s): %s\n", fprintf(stderr, "%s: chdir(%s): %s\n",
__FUNCTION__, pw->pw_dir, strerror(errno)); __FUNCTION__, pw->pw_dir, strerror(errno));
endpwent(); endpwent();
return -1; goto done;
} }
endpwent(); endpwent();
cli_signal_flush(h); cli_signal_flush(h);
cli_signal_unblock(h); cli_signal_unblock(h);
if (cmd){ if (cmd){
snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd); snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd);
if ((retval = system(bcmd)) < 0){ if (system(bcmd) < 0){
cli_signal_block(h); cli_signal_block(h);
fprintf(stderr, "%s: system(bash -c): %s\n", fprintf(stderr, "%s: system(bash -c): %s\n",
__FUNCTION__, strerror(errno)); __FUNCTION__, strerror(errno));
return -1; goto done;
} }
} }
else else
if ((retval = system("bash -l")) < 0){ if (system("bash -l") < 0){
cli_signal_block(h); cli_signal_block(h);
fprintf(stderr, "%s: system(bash): %s\n", fprintf(stderr, "%s: system(bash): %s\n",
__FUNCTION__, strerror(errno)); __FUNCTION__, strerror(errno));
return -1; goto done;
} }
cli_signal_block(h); cli_signal_block(h);
#if 0 /* Allow errcodes from bash */ #if 0 /* Allow errcodes from bash */
if (retval != 0){ if (retval != 0){
fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval); fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval);
return -1; goto done;
} }
#endif #endif
retval = 0;
return 0; done:
return retval;
} }
/*! Generic quit callback /*! Generic quit callback
@ -958,7 +959,7 @@ cli_notification_cb(int s,
event_unreg_fd(s, cli_notification_cb); event_unreg_fd(s, cli_notification_cb);
goto done; goto done;
} }
if (clicon_msg_decode(reply, &xt) < 0) if (clicon_msg_decode(reply, NULL, &xt) < 0) /* XXX pass yang_spec */
goto done; goto done;
if ((xe = xpath_first(xt, "//event")) != NULL){ if ((xe = xpath_first(xt, "//event")) != NULL){
x = NULL; x = NULL;

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -169,28 +169,6 @@ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, cbuf *cb,
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, cbuf *cb, char *helptext); yang_stmt *ytype, cbuf *cb, char *helptext);
/*! Patched maxstring to account for DEC64 types
* @note kludge to fix overflow error -> Fix the original error in cvtype_max2str
* by adding a fraction_digits argument.
*/
static char *
cvtype_max2str_dup2(enum cv_type type,
int fraction_digits)
{
int len;
char *str;
if (type!=CGV_DEC64 || fraction_digits==0)
return cvtype_max2str_dup(type);
if ((len = cvtype_max2str(type, NULL, 0)) < 0)
return NULL;
if ((str = (char *)malloc(len+1)) == NULL)
return NULL;
memset(str, '\0', len+1);
len = snprintf(str, len+1, "%" PRId64 ".0", (INT64_MAX/((int)pow(10,fraction_digits))));
return str;
}
/*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type /*! Generate CLI code for Yang leaf statement to CLIgen variable of specific type
* Check for completion (of already existent values), ranges (eg range[min:max]) and * Check for completion (of already existent values), ranges (eg range[min:max]) and
* patterns, (eg regexp:"[0.9]*"). * patterns, (eg regexp:"[0.9]*").
@ -208,18 +186,17 @@ yang2cli_var_sub(clicon_handle h,
char *helptext, char *helptext,
enum cv_type cvtype, enum cv_type cvtype,
int options, int options,
cg_var *mincv, cvec *cvv,
cg_var *maxcv,
char *pattern, char *pattern,
uint8_t fraction_digits uint8_t fraction_digits
) )
{ {
int retval = -1; int retval = -1;
char *type; char *type;
char *r;
yang_stmt *yi = NULL; yang_stmt *yi = NULL;
int i = 0; int i = 0;
char *cvtypestr; char *cvtypestr;
cg_var *cv;
if (cvtype == CGV_VOID){ if (cvtype == CGV_VOID){
retval = 0; retval = 0;
@ -276,47 +253,39 @@ yang2cli_var_sub(clicon_handle h,
if (options & YANG_OPTIONS_FRACTION_DIGITS) if (options & YANG_OPTIONS_FRACTION_DIGITS)
cprintf(cb, " fraction-digits:%u", fraction_digits); cprintf(cb, " fraction-digits:%u", fraction_digits);
if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){ if (options & (YANG_OPTIONS_RANGE|YANG_OPTIONS_LENGTH)){
assert(mincv || maxcv); /* Loop through range_min and range_min..rang_max */
i = 0;
while (i<cvec_len(cvv)){
// if (i)
// clicon_log(LOG_NOTICE, "%s: Warning %s has more ranges, ignoring", __FUNCTION__, ys->ys_argument);
cv = cvec_i(cvv, i++);
if (strcmp(cv_name_get(cv),"range_min") == 0){
cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length"); cprintf(cb, " %s[", (options&YANG_OPTIONS_RANGE)?"range":"length");
if (mincv){ cv2cbuf(cv, cb);
if ((r = cv2str_dup(mincv)) == NULL){ cprintf(cb,":");
clicon_err(OE_UNIX, errno, "cv2str_dup"); /* probe next */
if (i<cvec_len(cvv) &&
(cv = cvec_i(cvv, i)) != NULL &&
strcmp(cv_name_get(cv),"range_max") == 0){
i++;
cv2cbuf(cv, cb);
}
else /* If not, it is a single number range [x:x]*/
cv2cbuf(cv, cb);
cprintf(cb,"]");
}
}
}
if (options & YANG_OPTIONS_PATTERN){
char *posix = NULL;
if (regexp_xsd2posix(pattern, &posix) < 0)
goto done; goto done;
cprintf(cb, " regexp:\"%s\"", posix);
if (posix)
free(posix);
} }
cprintf(cb, "%s:", r);
free(r);
r = NULL;
}
if (maxcv != NULL){
if ((r = cv2str_dup(maxcv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
}
else{ /* Cligen does not have 'max' keyword in range so need to find actual
max value of type if yang range expression is 0..max
*/
if (cvtype==CGV_STRING){
if ((r = malloc(512)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
snprintf(r, 512, "%d", MAXPATHLEN);
}
else {
if ((r = cvtype_max2str_dup2(cvtype, fraction_digits)) == NULL){
clicon_err(OE_UNIX, errno, "cvtype_max2str");
goto done;
}
}
}
cprintf(cb, "%s]", r); /* range */
free(r);
r = NULL;
}
if (options & YANG_OPTIONS_PATTERN)
cprintf(cb, " regexp:\"%s\"", pattern);
cprintf(cb, ">"); cprintf(cb, ">");
if (helptext) if (helptext)
cprintf(cb, "(\"%s\")", helptext); cprintf(cb, "(\"%s\")", helptext);
@ -347,8 +316,7 @@ yang2cli_var_union_one(clicon_handle h,
{ {
int retval = -1; int retval = -1;
int options = 0; int options = 0;
cg_var *mincv = NULL; cvec *cvv = NULL;
cg_var *maxcv = NULL;
char *pattern = NULL; char *pattern = NULL;
uint8_t fraction_digits = 0; uint8_t fraction_digits = 0;
enum cv_type cvtype; enum cv_type cvtype;
@ -356,9 +324,9 @@ yang2cli_var_union_one(clicon_handle h,
char *restype; char *restype;
/* Resolve the sub-union type to a resolved type */ /* Resolve the sub-union type to a resolved type */
if (yang_type_resolve(ys, ytsub, /* in */ if (yang_type_resolve(ys, ys, ytsub, /* in */
&ytype, &options, /* resolved type */ &ytype, &options, /* resolved type */
&mincv, &maxcv, &pattern, &fraction_digits) < 0) &cvv, &pattern, &fraction_digits) < 0)
goto done; goto done;
restype = ytype?ytype->ys_argument:NULL; restype = ytype?ytype->ys_argument:NULL;
@ -367,10 +335,10 @@ yang2cli_var_union_one(clicon_handle h,
goto done; goto done;
} }
else { else {
if (clicon_type2cv(origtype, restype, &cvtype) < 0) if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done; goto done;
if ((retval = yang2cli_var_sub(h, ys, ytype, cb, helptext, cvtype, if ((retval = yang2cli_var_sub(h, ys, ytype, cb, helptext, cvtype,
options, mincv, maxcv, pattern, fraction_digits)) < 0) options, cvv, pattern, fraction_digits)) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
@ -438,8 +406,7 @@ yang2cli_var(clicon_handle h,
char *origtype; char *origtype;
yang_stmt *yrestype; /* resolved type */ yang_stmt *yrestype; /* resolved type */
char *restype; /* resolved type */ char *restype; /* resolved type */
cg_var *mincv = NULL; cvec *cvv = NULL;
cg_var *maxcv = NULL;
char *pattern = NULL; char *pattern = NULL;
uint8_t fraction_digits = 0; uint8_t fraction_digits = 0;
enum cv_type cvtype; enum cv_type cvtype;
@ -448,7 +415,7 @@ yang2cli_var(clicon_handle h,
char *type; char *type;
if (yang_type_get(ys, &origtype, &yrestype, if (yang_type_get(ys, &origtype, &yrestype,
&options, &mincv, &maxcv, &pattern, &fraction_digits) < 0) &options, &cvv, &pattern, &fraction_digits) < 0)
goto done; goto done;
restype = yrestype?yrestype->ys_argument:NULL; restype = yrestype?yrestype->ys_argument:NULL;
@ -456,7 +423,7 @@ yang2cli_var(clicon_handle h,
retval = 0; retval = 0;
goto done; goto done;
} }
if (clicon_type2cv(origtype, restype, &cvtype) < 0) if (clicon_type2cv(origtype, restype, ys, &cvtype) < 0)
goto done; goto done;
/* Note restype can be NULL here for example with unresolved hardcoded uuid */ /* Note restype can be NULL here for example with unresolved hardcoded uuid */
if (restype && strcmp(restype, "union") == 0){ if (restype && strcmp(restype, "union") == 0){
@ -485,7 +452,7 @@ yang2cli_var(clicon_handle h,
if (completionp) if (completionp)
cprintf(cb, "("); cprintf(cb, "(");
if ((retval = yang2cli_var_sub(h, ys, yrestype, cb, helptext, cvtype, if ((retval = yang2cli_var_sub(h, ys, yrestype, cb, helptext, cvtype,
options, mincv, maxcv, pattern, fraction_digits)) < 0) options, cvv, pattern, fraction_digits)) < 0)
goto done; goto done;
if (completionp){ if (completionp){
if (cli_expand_var_generate(h, ys, cvtype, cb, if (cli_expand_var_generate(h, ys, cvtype, cb,
@ -751,11 +718,6 @@ yang2cli_stmt(clicon_handle h,
if (yang_config(ys)){ if (yang_config(ys)){
switch (ys->ys_keyword){ switch (ys->ys_keyword){
case Y_GROUPING:
case Y_RPC:
case Y_AUGMENT:
return 0;
break;
case Y_CONTAINER: case Y_CONTAINER:
if (yang2cli_container(h, ys, cbuf, gt, level) < 0) if (yang2cli_container(h, ys, cbuf, gt, level) < 0)
goto done; goto done;
@ -773,19 +735,21 @@ yang2cli_stmt(clicon_handle h,
if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0) if (yang2cli_leaf(h, ys, cbuf, gt, level, 1) < 0)
goto done; goto done;
break; break;
default: case Y_CASE:
case Y_SUBMODULE:
case Y_MODULE:
for (i=0; i<ys->ys_len; i++) for (i=0; i<ys->ys_len; i++)
if ((yc = ys->ys_stmt[i]) != NULL) if ((yc = ys->ys_stmt[i]) != NULL)
if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0) if (yang2cli_stmt(h, yc, cbuf, gt, level+1) < 0)
goto done; goto done;
break; break;
default: /* skip */
break;
} }
} }
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
/*! Generate CLI code for Yang specification /*! Generate CLI code for Yang specification
@ -814,13 +778,13 @@ yang2cli(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
/* Traverse YANG specification: loop through statements */ /* Traverse YANG, loop through all modules and generate CLI */
for (i=0; i<yspec->yp_len; i++) for (i=0; i<yspec->yp_len; i++)
if ((ymod = yspec->yp_stmt[i]) != NULL){ if ((ymod = yspec->yp_stmt[i]) != NULL){
if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0) if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
goto done; goto done;
} }
clicon_debug(0, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf)); clicon_debug(2, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
/* Parse the buffer using cligen parser. XXX why this?*/ /* Parse the buffer using cligen parser. XXX why this?*/
if ((globals = cvec_new(0)) == NULL) if ((globals = cvec_new(0)) == NULL)
goto done; goto done;

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -71,7 +71,7 @@
#include "cli_handle.h" #include "cli_handle.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:" #define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:o:"
#define CLI_LOGFILE "/tmp/clixon_cli.log" #define CLI_LOGFILE "/tmp/clixon_cli.log"
@ -227,7 +227,8 @@ usage(clicon_handle h,
"\t-l <s|e|o|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n" "\t-l <s|e|o|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n" "\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n"
"\t-c <file>\tSpecify cli spec file.\n" "\t-c <file>\tSpecify cli spec file.\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n", "\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0, argv0,
plgdir ? plgdir : "none" plgdir ? plgdir : "none"
); );
@ -403,6 +404,15 @@ main(int argc, char **argv)
if (clicon_username_set(h, optarg) < 0) if (clicon_username_set(h, optarg) < 0)
goto done; goto done;
break; break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -446,6 +456,9 @@ main(int argc, char **argv)
if (yang_spec_load_dir(h, str, yspec) < 0) if (yang_spec_load_dir(h, str, yspec) < 0)
goto done; goto done;
} }
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */ /* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0) if (yang_modules_init(h) < 0)

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -171,7 +171,7 @@ expand_dbvar(void *h,
/* This is primarily to get "y", /* This is primarily to get "y",
* xpath2xml would have worked!! * xpath2xml would have worked!!
*/ */
if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 1)
goto done; goto done;
if (y==NULL) if (y==NULL)
goto ok; goto ok;
@ -443,6 +443,7 @@ cli_show_config(clicon_handle h,
cxobj *xc; cxobj *xc;
cxobj *xerr; cxobj *xerr;
enum genmodel_type gt; enum genmodel_type gt;
yang_spec *yspec;
if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ if (cvec_len(argv) != 3 && cvec_len(argv) != 4){
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv)); clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv));
@ -496,6 +497,13 @@ cli_show_config(clicon_handle h,
clicon_rpc_generate_error("Get configuration", xerr); clicon_rpc_generate_error("Get configuration", xerr);
goto done; goto done;
} }
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* Some formats (eg cli) require yang */
if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
/* Print configuration according to format */ /* Print configuration according to format */
switch (format){ switch (format){
case FORMAT_XML: case FORMAT_XML:
@ -516,7 +524,7 @@ cli_show_config(clicon_handle h,
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done; goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL) while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL)
xml2cli(stdout, xc, NULL, gt); /* cli syntax */ xml2cli(stdout, xc, NULL, gt); /* cli syntax */
break; break;
case FORMAT_NETCONF: case FORMAT_NETCONF:

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -44,6 +44,7 @@
* (Duplicated. Also in netconf_*.h) * (Duplicated. Also in netconf_*.h)
*/ */
int netconf_output(int s, cbuf *xf, char *msg); int netconf_output(int s, cbuf *xf, char *msg);
int netconf_output_encap(int s, cbuf *xf, char *msg);
int netconf_xpath(cxobj *xsearch, int netconf_xpath(cxobj *xsearch,
cxobj *xfilter, cxobj *xfilter,

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -182,9 +182,9 @@ netconf_get_target(cxobj *xn,
* @param[in] s * @param[in] s
* @param[in] cb Cligen buffer that contains the XML message * @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug * @param[in] msg Only for debug
* @note Assumes "cb" contains valid XML, ie encoding is correct. This is done * @retval 0 OK
* if it is output by a xml render routine (xml_print et al), but NOT * @retval -1 Error
* otherwise. * @see netconf_output_encap for function with encapsulation
*/ */
int int
netconf_output(int s, netconf_output(int s,
@ -216,3 +216,34 @@ netconf_output(int s,
return retval; return retval;
} }
/*! Encapsulate and send outgoing netconf packet as cbuf on socket
* @param[in] s
* @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug
* @retval 0 OK
* @retval -1 Error
* @note Assumes "cb" contains valid XML
* @see netconf_output without encapsulation
*/
int
netconf_output_encap(int s,
cbuf *cb,
char *msg)
{
int retval = -1;
cbuf *cb1 = NULL;
if ((cb1 = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
add_preamble(cb1);
cprintf(cb1, "%s", cbuf_get(cb));
add_postamble(cb1);
retval = netconf_output(s, cb1, msg);
done:
if (cb1)
cbuf_free(cb1);
return retval;
}

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -75,5 +75,6 @@ int add_error_preamble(cbuf *xf, char *reason);
char *netconf_get_target(cxobj *xn, char *path); char *netconf_get_target(cxobj *xn, char *path);
int add_error_postamble(cbuf *xf); int add_error_postamble(cbuf *xf);
int netconf_output(int s, cbuf *xf, char *msg); int netconf_output(int s, cbuf *xf, char *msg);
int netconf_output_encap(int s, cbuf *cb, char *msg);
#endif /* _NETCONF_LIB_H_ */ #endif /* _NETCONF_LIB_H_ */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -71,7 +71,7 @@
#include "netconf_rpc.h" #include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:" #define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:o:"
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log" #define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
@ -80,9 +80,10 @@
* @param[in] cb Packet buffer * @param[in] cb Packet buffer
*/ */
static int static int
process_incoming_packet(clicon_handle h, netconf_input_packet(clicon_handle h,
cbuf *cb) cbuf *cb)
{ {
int retval = -1;
char *str; char *str;
char *str0; char *str0;
cxobj *xreq = NULL; /* Request (in) */ cxobj *xreq = NULL; /* Request (in) */
@ -92,9 +93,12 @@ process_incoming_packet(clicon_handle h,
cxobj *xrpc; cxobj *xrpc;
cxobj *xc; cxobj *xc;
yang_spec *yspec; yang_spec *yspec;
int ret;
cxobj *xa;
cxobj *xa2;
clicon_debug(1, "RECV"); clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); clicon_debug(2, "%s: \"%s\"", __FUNCTION__, cbuf_get(cb));
if ((cbret = cbuf_new()) == NULL){ if ((cbret = cbuf_new()) == NULL){
clicon_err(LOG_ERR, errno, "cbuf_new"); clicon_err(LOG_ERR, errno, "cbuf_new");
goto done; goto done;
@ -108,22 +112,21 @@ process_incoming_packet(clicon_handle h,
/* Parse incoming XML message */ /* Parse incoming XML message */
if (xml_parse_string(str, yspec, &xreq) < 0){ if (xml_parse_string(str, yspec, &xreq) < 0){
free(str0); free(str0);
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) if (netconf_operation_failed(cbret, "rpc", clicon_err_reason)< 0)
goto done; goto done;
netconf_output(1, cbret, "rpc-error"); netconf_output_encap(1, cbret, "rpc-error");
goto done; goto done;
} }
free(str0); free(str0);
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
int ret;
isrpc++; isrpc++;
if ((ret = xml_yang_validate_rpc(xrpc)) < 0) if (xml_spec_populate_rpc(h, xrpc, yspec) < 0)
goto done;
if ((ret = xml_yang_validate_rpc(xrpc, cbret)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) netconf_output_encap(1, cbret, "rpc-error");
goto done; goto ok;
netconf_output(1, cbret, "rpc-error");
goto done;
} }
} }
else else
@ -142,8 +145,13 @@ process_incoming_packet(clicon_handle h,
goto done; goto done;
} }
else{ /* there is a return message in xret */ else{ /* there is a return message in xret */
cxobj *xa, *xa2;
assert(xret); if (xret == NULL){
if (netconf_operation_failed(cbret, "rpc", "Internal error: no xml return")< 0)
goto done;
netconf_output_encap(1, cbret, "rpc-error");
goto done;
}
if ((xc = xml_child_i(xret,0))!=NULL){ if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL; xa=NULL;
/* Copy message-id attribute from incoming to reply. /* Copy message-id attribute from incoming to reply.
@ -158,16 +166,15 @@ process_incoming_packet(clicon_handle h,
if (xml_addsub(xc, xa2) < 0) if (xml_addsub(xc, xa2) < 0)
goto done; goto done;
} }
add_preamble(cbret);
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
add_postamble(cbret); if (netconf_output_encap(1, cbret, "rpc-reply") < 0){
if (netconf_output(1, cbret, "rpc-reply") < 0){
cbuf_free(cbret); cbuf_free(cbret);
goto done; goto done;
} }
} }
} }
ok:
retval = 0;
done: done:
if (xreq) if (xreq)
xml_free(xreq); xml_free(xreq);
@ -175,7 +182,7 @@ process_incoming_packet(clicon_handle h,
xml_free(xret); xml_free(xret);
if (cbret) if (cbret)
cbuf_free(cbret); cbuf_free(cbret);
return 0; return retval;
} }
/*! Get netconf message: detect end-of-msg /*! Get netconf message: detect end-of-msg
@ -228,8 +235,8 @@ netconf_input_cb(int s,
/* OK, we have an xml string from a client */ /* OK, we have an xml string from a client */
/* Remove trailer */ /* Remove trailer */
*(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0'; *(((char*)cbuf_get(cb)) + cbuf_len(cb) - strlen("]]>]]>")) = '\0';
if (process_incoming_packet(h, cb) < 0) if (netconf_input_packet(h, cb) < 0)
goto done; ; //goto done; // ignore errors
if (cc_closed) if (cc_closed)
break; break;
cbuf_reset(cb); cbuf_reset(cb);
@ -326,7 +333,8 @@ usage(clicon_handle h,
"\t-y <file>\tLoad yang spec file (override yang main module)\n" "\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n" "\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-t <sec>\tTimeout in seconds. Quit after this time.\n", "\t-t <sec>\tTimeout in seconds. Quit after this time.\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0, argv0,
clicon_netconf_dir(h) clicon_netconf_dir(h)
); );
@ -439,7 +447,15 @@ main(int argc,
case 't': /* timeout in seconds */ case 't': /* timeout in seconds */
tv.tv_sec = atoi(optarg); tv.tv_sec = atoi(optarg);
break; break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -468,7 +484,9 @@ main(int argc,
if (yang_spec_load_dir(h, str, yspec) < 0) if (yang_spec_load_dir(h, str, yspec) < 0)
goto done; goto done;
} }
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */ /* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0) if (yang_modules_init(h) < 0)
goto done; goto done;

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -56,6 +56,7 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <syslog.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/param.h> #include <sys/param.h>
@ -134,20 +135,10 @@ netconf_get_config(clicon_handle h,
{ {
cxobj *xfilter; /* filter */ cxobj *xfilter; /* filter */
int retval = -1; int retval = -1;
char *source;
char *ftype = NULL; char *ftype = NULL;
cxobj *xfilterconf; cxobj *xfilterconf;
cxobj *xconf; cxobj *xconf;
if ((source = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>source</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
/* ie <filter>...</filter> */ /* ie <filter>...</filter> */
if ((xfilter = xpath_first(xn, "filter")) != NULL) if ((xfilter = xpath_first(xn, "filter")) != NULL)
ftype = xml_find_value(xfilter, "type"); ftype = xml_find_value(xfilter, "type");
@ -186,7 +177,6 @@ netconf_get_config(clicon_handle h,
"<error-info>type</error-info>" "<error-info>type</error-info>"
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
} }
ok: /* netconf error is not fatal */
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -222,11 +212,9 @@ get_edit_opts(cxobj *xn,
if ((optstr = xml_body(x)) != NULL){ if ((optstr = xml_body(x)) != NULL){
if (strcmp(optstr, "test-then-set") == 0) if (strcmp(optstr, "test-then-set") == 0)
*testopt = TEST_THEN_SET; *testopt = TEST_THEN_SET;
else else if (strcmp(optstr, "set") == 0)
if (strcmp(optstr, "set") == 0)
*testopt = SET; *testopt = SET;
else else if (strcmp(optstr, "test-only") == 0)
if (strcmp(optstr, "test-only") == 0)
*testopt = TEST_ONLY; *testopt = TEST_ONLY;
else else
goto parerr; goto parerr;
@ -310,53 +298,18 @@ netconf_edit_config(clicon_handle h,
{ {
int retval = -1; int retval = -1;
int optret; int optret;
enum operation_type operation = OP_MERGE;
enum test_option testopt = TEST_THEN_SET;/* only supports this */ enum test_option testopt = TEST_THEN_SET;/* only supports this */
enum error_option erropt = STOP_ON_ERROR; /* only supports this */ enum error_option erropt = STOP_ON_ERROR; /* only supports this */
cxobj *xc; /* config */
cxobj *x;
cxobj *xfilter;
char *ftype = NULL;
char *target; /* db */
/* must have target, and it should be candidate */
if ((target = netconf_get_target(xn, "target")) == NULL ||
strcmp(target, "candidate")){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
/* CLICON addition, eg <filter type="restconf" select=<api-path> /> */
if ((xfilter = xpath_first(xn, "filter")) != NULL) {
if ((ftype = xml_find_value(xfilter, "type")) != NULL)
if (strcmp(ftype,"restconf")){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
}
if ((x = xpath_first(xn, "default-operation")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
}
if ((optret = get_edit_opts(xn, &testopt, &erropt, xret)) < 0) if ((optret = get_edit_opts(xn, &testopt, &erropt, xret)) < 0)
goto done; goto done;
if (optret == 0) /* error in opt parameters */ if (optret == 0) /* error in opt parameters */
goto ok; goto ok;
/* not supported opts */ /* These constraints are clixon-specific since :validate should
* support all testopts, and erropts should be supported
* And therefore extends the validation
* (implement the features before removing these checks)
*/
if (testopt!=TEST_THEN_SET || erropt!=STOP_ON_ERROR){ if (testopt!=TEST_THEN_SET || erropt!=STOP_ON_ERROR){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>" xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>operation-not-supported</error-tag>" "<error-tag>operation-not-supported</error-tag>"
@ -365,66 +318,6 @@ netconf_edit_config(clicon_handle h,
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
goto ok; goto ok;
} }
/* operation is OP_REPLACE, OP_MERGE, or OP_NONE pass all to backend */
if ((xc = xpath_first(xn, "config")) != NULL){
#if 0
/* application-specific code registers 'config' */
if ((ret = netconf_plugin_callbacks(h, xc, xret)) < 0){
goto ok;
}
#endif
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
}
ok:
retval = 0;
done:
return retval;
}
/*! Netconf copy configuration
<copy-config>
<target>
<candidate/>
</target>
<source>
<url>
<!- - location specifier for file containing the new configuration - ->
</url>
</source>
<copy-config>
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_copy_config(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *source;
char *target; /* filenames */
if ((source = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>source</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if ((target = netconf_get_target(xn, "target")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done; goto done;
ok: ok:
@ -433,99 +326,6 @@ netconf_copy_config(clicon_handle h,
return retval; return retval;
} }
/*! Delete configuration
<delete-config>
<target>
<candidate/>
</target>
</delete-config>
Delete a configuration datastore. The <running>
configuration datastore cannot be deleted.
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_delete_config(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
char *target; /* filenames */
int retval = -1;
if ((target = netconf_get_target(xn, "target")) == NULL ||
strcmp(target, "running")==0){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Lock a database
<lock>
<target>
<candidate/>
</target>
</lock>
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_lock(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *target;
if ((target = netconf_get_target(xn, "target")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Unlock a database
<unlock>
<target>
<candidate/>
</target>
</unlock>
XXX
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_unlock(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
return netconf_lock(h, xn, xret);
}
/*! Get running configuration and device state information /*! Get running configuration and device state information
* *
* *
@ -589,133 +389,6 @@ netconf_get(clicon_handle h,
"<error-info>type</error-info>" "<error-info>type</error-info>"
"</rpc-error></rpc-reply>"); "</rpc-error></rpc-reply>");
} }
// ok: /* netconf error is not fatal */
retval = 0;
done:
return retval;
}
/*! Close a (user) session
<close-session/>
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_close_session(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
cc_closed++;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Kill other user sessions
<kill-session>
<session-id>PID</session-id>
</kill-session>
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_kill_session(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval=-1;
cxobj *xs;
if ((xs = xpath_first(xn, "//session-id")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>session-id</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Check the semantic consistency of candidate
<validate/>
:validate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_validate(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
char *target;
if ((target = netconf_get_target(xn, "source")) == NULL){
xml_parse_va(xret, NULL, "<rpc-reply><rpc-error>"
"<error-tag>missing-element</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-info><bad-element>target</bad-element></error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
ok:
retval = 0;
done:
return retval;
}
/*! Commit candidate -> running
<commit/>
:candidate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_commit(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Discard all changes in candidate / revert to running
<discard-changes/>
:candidate
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
*/
static int
netconf_discard_changes(clicon_handle h,
cxobj *xn,
cxobj **xret)
{
int retval = -1;
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0)
goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -753,6 +426,8 @@ netconf_notification_cb(int s,
cbuf *cb; cbuf *cb;
cxobj *xn = NULL; /* event xml */ cxobj *xn = NULL; /* event xml */
cxobj *xt = NULL; /* top xml */ cxobj *xt = NULL; /* top xml */
clicon_handle h = (clicon_handle)arg;
yang_spec *yspec = NULL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */ /* get msg (this is the reason this function is called) */
@ -766,7 +441,8 @@ netconf_notification_cb(int s,
event_unreg_fd(s, netconf_notification_cb); event_unreg_fd(s, netconf_notification_cb);
goto done; goto done;
} }
if (clicon_msg_decode(reply, &xt) < 0) yspec = clicon_dbspec_yang(h);
if (clicon_msg_decode(reply, yspec, &xt) < 0)
goto done; goto done;
if ((xn = xpath_first(xt, "notification")) == NULL) if ((xn = xpath_first(xt, "notification")) == NULL)
goto ok; goto ok;
@ -775,12 +451,10 @@ netconf_notification_cb(int s,
clicon_err(OE_PLUGIN, errno, "cbuf_new"); clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done; goto done;
} }
add_preamble(cb); /* Make it well-formed netconf xml */
if (clicon_xml2cbuf(cb, xn, 0, 0) < 0) if (clicon_xml2cbuf(cb, xn, 0, 0) < 0)
goto done; goto done;
add_postamble(cb);
/* Send it to listening client on stdout */ /* Send it to listening client on stdout */
if (netconf_output(1, cb, "notification") < 0){ if (netconf_output_encap(1, cb, "notification") < 0){
cbuf_free(cb); cbuf_free(cb);
goto done; goto done;
} }
@ -840,7 +514,7 @@ netconf_create_subscription(clicon_handle h,
goto ok; goto ok;
if (event_reg_fd(s, if (event_reg_fd(s,
netconf_notification_cb, netconf_notification_cb,
NULL, h,
"notification socket") < 0) "notification socket") < 0)
goto done; goto done;
ok: ok:
@ -882,6 +556,10 @@ netconf_application_rpc(clicon_handle h,
clicon_err(OE_UNIX, 0, "cbuf_new"); clicon_err(OE_UNIX, 0, "cbuf_new");
goto done; goto done;
} }
if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
/* Find yang rpc statement, return yang rpc statement if found /* Find yang rpc statement, return yang rpc statement if found
Check application RPC */ Check application RPC */
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -902,13 +580,9 @@ netconf_application_rpc(clicon_handle h,
goto ok; goto ok;
} }
yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn));
if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ if ((yrpc==NULL) && !_CLICON_XML_NS_STRICT){
int i; if (xml_yang_find_non_strict(xn, yspec, &yrpc) < 0) /* Y_RPC */
for (i=0; i<yspec->yp_len; i++){ goto done;
ymod = yspec->yp_stmt[i];
if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL)
break;
}
} }
/* Check if found */ /* Check if found */
if (yrpc != NULL){ if (yrpc != NULL){
@ -917,15 +591,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */
if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done; goto done;
if (xml_apply(xn, CX_ELMNT, if ((ret = xml_yang_validate_all_top(xn, cbret)) < 0)
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done;
if (xml_yang_validate_add(xn, NULL) < 0)
goto done; goto done;
if (ret == 0){
netconf_output_encap(1, cbret, "rpc-error");
goto ok;
} }
if ((cbret = cbuf_new()) == NULL){ if ((ret = xml_yang_validate_add(xn, cbret)) < 0)
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done; goto done;
if (ret == 0){
netconf_output_encap(1, cbret, "rpc-error");
goto ok;
}
} }
/* Look for local (client-side) netconf plugins. */ /* Look for local (client-side) netconf plugins. */
if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0)
@ -943,11 +620,18 @@ netconf_application_rpc(clicon_handle h,
xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done; goto done;
if (xml_apply(xoutput, CX_ELMNT, if ((ret = xml_yang_validate_all_top(xoutput, cbret)) < 0)
(xml_applyfn_t*)xml_yang_validate_all, NULL) < 0)
goto done; goto done;
if (xml_yang_validate_add(xoutput, NULL) < 0) if (ret == 0){
clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
goto ok;
}
if ((ret = xml_yang_validate_add(xoutput, cbret)) < 0)
goto done; goto done;
if (ret == 0){
clicon_log(LOG_WARNING, "Errors in output netconf %s", cbuf_get(cbret));
goto ok;
}
} }
retval = 1; /* handled by callback */ retval = 1; /* handled by callback */
goto done; goto done;
@ -992,9 +676,25 @@ netconf_rpc_dispatch(clicon_handle h,
if (xml_value_set(xa, username) < 0) if (xml_value_set(xa, username) < 0)
goto done; goto done;
} }
/* Many of these calls are now calling generic clicon_rpc_netconf_xml
* directly, since the validation is generic and done before this place
* in the call. Some call however need extra validation, such as the
* filter parameter to get/get-config and tes- err-opts of edit-config.
*/
xe = NULL; xe = NULL;
while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) {
if (strcmp(xml_name(xe), "get-config") == 0){ if (strcmp(xml_name(xe), "copy-config") == 0 ||
strcmp(xml_name(xe), "delete-config") == 0 ||
strcmp(xml_name(xe), "lock") == 0 ||
strcmp(xml_name(xe), "unlock") == 0 ||
strcmp(xml_name(xe), "kill-session") == 0 ||
strcmp(xml_name(xe), "validate") == 0 || /* :validate */
strcmp(xml_name(xe), "commit") == 0 || /* :candidate */
strcmp(xml_name(xe), "discard-changes") == 0){
if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "get-config") == 0){
if (netconf_get_config(h, xe, xret) < 0) if (netconf_get_config(h, xe, xret) < 0)
goto done; goto done;
} }
@ -1002,46 +702,13 @@ netconf_rpc_dispatch(clicon_handle h,
if (netconf_edit_config(h, xe, xret) < 0) if (netconf_edit_config(h, xe, xret) < 0)
goto done; goto done;
} }
else if (strcmp(xml_name(xe), "copy-config") == 0){
if (netconf_copy_config(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "delete-config") == 0){
if (netconf_delete_config(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "lock") == 0) {
if (netconf_lock(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "unlock") == 0){
if (netconf_unlock(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "get") == 0){ else if (strcmp(xml_name(xe), "get") == 0){
if (netconf_get(h, xe, xret) < 0) if (netconf_get(h, xe, xret) < 0)
goto done; goto done;
} }
else if (strcmp(xml_name(xe), "close-session") == 0){ else if (strcmp(xml_name(xe), "close-session") == 0){
if (netconf_close_session(h, xe, xret) < 0) cc_closed++;
goto done; if (clicon_rpc_netconf_xml(h, xml_parent(xe), xret, NULL) < 0)
}
else if (strcmp(xml_name(xe), "kill-session") == 0) {
if (netconf_kill_session(h, xe, xret) < 0)
goto done;
}
/* Validate capability :validate */
else if (strcmp(xml_name(xe), "validate") == 0){
if (netconf_validate(h, xe, xret) < 0)
goto done;
}
/* Candidate configuration capability :candidate */
else if (strcmp(xml_name(xe), "commit") == 0){
if (netconf_commit(h, xe, xret) < 0)
goto done;
}
else if (strcmp(xml_name(xe), "discard-changes") == 0){
if (netconf_discard_changes(h, xe, xret) < 0)
goto done; goto done;
} }
/* RFC 5277 :notification */ /* RFC 5277 :notification */

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,11 +1,11 @@
# Clixon Restconf # Clixon Restconf
* [Installation](#Installation) * [Installation](#installation)
* [Streams](Streams) * [Streams](#streams)
* [Nchan Streams](Nchan) * [Nchan Streams](#nchan)
* [Debugging](Debugging) * [Debugging](#debugging)
### 1. Installation ## Installation
The examples are based on Nginx. Other reverse proxies should work but are not verified. The examples are based on Nginx. Other reverse proxies should work but are not verified.
@ -44,39 +44,39 @@ sudo systemctl start start.service
Start clixon restconf daemon Start clixon restconf daemon
``` ```
olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data > sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
``` ```
Make restconf calls with curl Make restconf calls with curl
``` ```
olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces > curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces
[ [
{ {
"interfaces": { "ietf-interfaces:interfaces": {
"interface":[ "interface":[
{ {
"name": "eth0",
"type": "eth",
"enabled": "true",
"name": "eth9", "name": "eth9",
"type": "eth", "type": "ex:eth",
"enabled": "true" "enabled": true,
} }
] ]
} }
} }
] ]
olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type ```
[ Get the type of a specific interface:
{ ```
"type": "eth" > curl -G http://127.0.0.1/restconf/data/interfaces/interface=eth9/type
} {
] "ietf-interfaces:type": "eth"
}
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data ```
Example of writing a new interfaces specification:
```
curl -sX PUT http://localhost/restconf/data -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth1","type":"ex:eth","enabled":true}}}'
``` ```
### 2. Streams ## Streams
Clixon have two experimental restconf event stream implementations following Clixon have two experimental restconf event stream implementations following
RFC8040 Section 6 using SSE. One native and one using Nginx RFC8040 Section 6 using SSE. One native and one using Nginx
@ -112,7 +112,7 @@ Add the following to extend the nginx configuration file with the following stat
AN example of a stream access is as follows: AN example of a stream access is as follows:
``` ```
vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE > curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:11.373124</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification> data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:11.373124</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:16.375265</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification> data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:16.375265</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
@ -125,7 +125,7 @@ You can also specify start and stop time. Start-time enables replay of existing
See (stream tests)[../test/test_streams.sh] for more examples. See (stream tests)[../test/test_streams.sh] for more examples.
### 3. Nchan ## Nchan
As an alternative streams implementation, Nginx/Nchan can be used. As an alternative streams implementation, Nginx/Nchan can be used.
Nginx uses pub/sub channels and can be configured in a variety of Nginx uses pub/sub channels and can be configured in a variety of
@ -180,7 +180,7 @@ curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET h
See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint. See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint.
### 4. Debugging ## Debugging
Start the restconf fastcgi program with debug flag: Start the restconf fastcgi program with debug flag:
``` ```

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -71,10 +71,11 @@ static const map_str2int netconf_restconf_map[] = {
{"missing-attribute", 400}, {"missing-attribute", 400},
{"bad-attribute", 400}, {"bad-attribute", 400},
{"unknown-attribute", 400}, {"unknown-attribute", 400},
{"missing-element", 400},
{"bad-element", 400}, {"bad-element", 400},
{"unknown-element", 400}, {"unknown-element", 400},
{"unknown-namespace", 400}, {"unknown-namespace", 400},
{"access-denied", 401}, {"access-denied", 401}, /* or 403 */
{"access-denied", 403}, {"access-denied", 403},
{"lock-denied", 409}, {"lock-denied", 409},
{"resource-denied", 409}, {"resource-denied", 409},
@ -436,7 +437,8 @@ api_return_err(clicon_handle h,
goto ok; goto ok;
} }
tagstr = xml_body(xtag); tagstr = xml_body(xtag);
code = restconf_err2code(tagstr); if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
if ((reason_phrase = restconf_code2reason(code)) == NULL) if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase=""; reason_phrase="";
if (xml_name_set(xerr, "error") < 0) if (xml_name_set(xerr, "error") < 0)
@ -448,9 +450,12 @@ api_return_err(clicon_handle h,
else else
if (xml2json_cbuf(cb, xerr, pretty) < 0) if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done; goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
FCGX_SetExitStatus(code, r->out); /* Created */
FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n",
use_xml?"xml":"json"); use_xml?"xml":"json");
if (use_xml){ if (use_xml){
if (pretty){ if (pretty){
FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb)); FCGX_FPrintF(r->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -81,7 +81,7 @@
#include "restconf_stream.h" #include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:y:a:u:" #define RESTCONF_OPTS "hD:f:l:p:y:a:u:o:"
/* RESTCONF enables deployments to specify where the RESTCONF API is /* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta" located. The client discovers this by getting the "/.well-known/host-meta"
@ -495,7 +495,8 @@ usage(clicon_handle h,
"\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n" "\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n" "\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n" "\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n", "\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n"
"\t-o \"<option>=<value>\"\tGive configuration option overriding config file (see clixon-config.yang)\n",
argv0, argv0,
clicon_restconf_dir(h) clicon_restconf_dir(h)
); );
@ -611,6 +612,15 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg); clicon_option_str_set(h, "CLICON_SOCK", optarg);
break; break;
case 'o':{ /* Configuration option */
char *val;
if ((val = index(optarg, '=')) == NULL)
usage(h, argv0);
*val++ = '\0';
if (clicon_option_add(h, optarg, val) < 0)
goto done;
break;
}
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -645,7 +655,9 @@ main(int argc,
if (yang_spec_load_dir(h, str, yspec) < 0) if (yang_spec_load_dir(h, str, yspec) < 0)
goto done; goto done;
} }
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */ /* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0) if (yang_modules_init(h) < 0)
goto done; goto done;
@ -654,7 +666,7 @@ main(int argc,
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0) yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
goto done; goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done; goto done;
/* Call start function in all plugins before we go interactive /* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start Pass all args after the standard options to plugin_start

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -180,7 +180,7 @@ restconf_stream_cb(int s,
clicon_exit_set(); clicon_exit_set();
goto done; goto done;
} }
if (clicon_msg_decode(reply, &xtop) < 0) if (clicon_msg_decode(reply, NULL, &xtop) < 0) /* XXX pass yang_spec */
goto done; goto done;
/* create event */ /* create event */
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
@ -249,7 +249,7 @@ restconf_stream(clicon_handle h,
clicon_err(OE_XML, errno, "cbuf_new"); clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
cprintf(cb, "<rpc><create-subscription><stream>%s</stream>", name); cprintf(cb, "<rpc><create-subscription xmlns=\"urn:ietf:params:xml:ns:netmod:notification\"><stream>%s</stream>", name);
/* Print all fields */ /* Print all fields */
for (i=0; i<cvec_len(qvec); i++){ for (i=0; i<cvec_len(qvec); i++){
cv = cvec_i(qvec, i); cv = cvec_i(qvec, i);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -106,7 +106,7 @@ main(int argc, char **argv)
char *plugin = NULL; char *plugin = NULL;
char *cmd = NULL; char *cmd = NULL;
yang_spec *yspec = NULL; yang_spec *yspec = NULL;
char *yangmodule = NULL; char *yangfilename = NULL;
char *dbdir = NULL; char *dbdir = NULL;
int ret; int ret;
int pid; int pid;
@ -151,7 +151,7 @@ main(int argc, char **argv)
case 'y': /* Yang file */ case 'y': /* Yang file */
if (!optarg) if (!optarg)
usage(argv0); usage(argv0);
yangmodule = optarg; yangfilename = optarg;
break; break;
} }
/* /*
@ -173,8 +173,8 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Missing dbdir -b option"); clicon_err(OE_DB, 0, "Missing dbdir -b option");
goto done; goto done;
} }
if (yangmodule == NULL){ if (yangfilename == NULL){
clicon_err(OE_YANG, 0, "Missing yang module -m option"); clicon_err(OE_YANG, 0, "Missing yang filename -y option");
goto done; goto done;
} }
/* Load datastore plugin */ /* Load datastore plugin */
@ -187,7 +187,7 @@ main(int argc, char **argv)
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
/* Parse yang spec from given file */ /* Parse yang spec from given file */
if (yang_parse(h, yangmodule, NULL, NULL, yspec) < 0) if (yang_spec_parse_file(h, yangfilename, yspec) < 0)
goto done; goto done;
/* Set database directory option */ /* Set database directory option */
if (xmldb_setopt(h, "dbdir", dbdir) < 0) if (xmldb_setopt(h, "dbdir", dbdir) < 0)
@ -239,15 +239,18 @@ main(int argc, char **argv)
clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]); clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]);
usage(argv0); usage(argv0);
} }
_CLICON_XML_NS_ITERATE = 1; _CLICON_XML_NS_STRICT = 0;
if (xml_parse_string(argv[2], NULL, &xt) < 0) if (xml_parse_string(argv[2], NULL, &xt) < 0)
goto done; goto done;
if (xml_rootchild(xt, 0, &xt) < 0) if (xml_rootchild(xt, 0, &xt) < 0)
goto done; goto done;
if ((cbret = cbuf_new()) == NULL) if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
if (xmldb_put(h, db, op, xt, cbret) < 0) }
if (xmldb_put(h, db, op, xt, cbret) < 1)
goto done; goto done;
} }
else if (strcmp(cmd, "copy")==0){ else if (strcmp(cmd, "copy")==0){
if (argc != 2) if (argc != 2)

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -358,6 +358,16 @@ xml_copy_marked(cxobj *x0,
assert(x0 && x1); assert(x0 && x1);
yt = xml_spec(x0); /* can be null */ yt = xml_spec(x0); /* can be null */
/* Copy all attributes */
x = NULL;
while ((x = xml_child_each(x0, x, CX_ATTR)) != NULL) {
name = xml_name(x);
if ((xcopy = xml_new(name, x1, xml_spec(x))) == NULL)
goto done;
if (xml_copy(x, xcopy) < 0)
goto done;
}
/* Go through children to detect any marked nodes: /* Go through children to detect any marked nodes:
* (3) Special case: key nodes in lists are copied if any * (3) Special case: key nodes in lists are copied if any
* node in list is marked * node in list is marked
@ -470,6 +480,10 @@ text_get(xmldb_handle xh,
if (singleconfigroot(xt, &xt) < 0) if (singleconfigroot(xt, &xt) < 0)
goto done; goto done;
} }
/* XXX: should we validate file if read from disk?
* Argument against: we may want to have a semantically wrong file and wish
* to edit?
*/
} /* xt == NULL */ } /* xt == NULL */
/* Here xt looks like: <config>...</config> */ /* Here xt looks like: <config>...</config> */
@ -530,12 +544,9 @@ text_get(xmldb_handle xh,
/* Add default values (if not set) */ /* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done; goto done;
/* Order XML children according to YANG */ #if 1 /* debug */
if (!xml_child_sort && xml_apply(xt, CX_ELMNT, xml_order, NULL) < 0) if (xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
goto done; clicon_log(LOG_NOTICE, "%s: sort verify failed #2", __FUNCTION__);
#if 0 /* debug */
if (xml_child_sort && xml_apply0(xt, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #2", __FUNCTION__);
#endif #endif
if (debug>1) if (debug>1)
clicon_xml2file(stderr, xt, 0, 1); clicon_xml2file(stderr, xt, 0, 1);
@ -555,17 +566,22 @@ text_get(xmldb_handle xh,
} }
/*! Modify a base tree x0 with x1 with yang spec y according to operation op /*! Modify a base tree x0 with x1 with yang spec y according to operation op
* @param[in] th text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL
* @param[in] x0p Parent of x0 * @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base * @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[out] cbret Initialized cligen buffer. Contains return XML or "". * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* Assume x0 and x1 are same on entry and that y is the spec * Assume x0 and x1 are same on entry and that y is the spec
* @see put in clixon_keyvalue.c * @see text_modify_top
*/ */
static int static int
text_modify(cxobj *x0, text_modify(struct text_handle *th,
cxobj *x0,
yang_node *y0, yang_node *y0,
cxobj *x0p, cxobj *x0p,
cxobj *x1, cxobj *x1,
@ -576,6 +592,8 @@ text_modify(cxobj *x0,
char *opstr; char *opstr;
char *x1name; char *x1name;
char *x1cname; /* child name */ char *x1cname; /* child name */
cxobj *x0a; /* attribute */
cxobj *x1a; /* attribute */
cxobj *x0c; /* base child */ cxobj *x0c; /* base child */
cxobj *x0b; /* base body */ cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */ cxobj *x1c; /* mod child */
@ -583,6 +601,7 @@ text_modify(cxobj *x0,
yang_stmt *yc; /* yang child */ yang_stmt *yc; /* yang child */
cxobj **x0vec = NULL; cxobj **x0vec = NULL;
int i; int i;
int ret;
assert(x1 && xml_type(x1) == CX_ELMNT); assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0); assert(y0);
@ -598,7 +617,7 @@ text_modify(cxobj *x0,
if (x0){ if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done; goto done;
goto ok; goto fail;
} }
case OP_NONE: /* fall thru */ case OP_NONE: /* fall thru */
case OP_MERGE: case OP_MERGE:
@ -607,6 +626,13 @@ text_modify(cxobj *x0,
// int iamkey=0; // int iamkey=0;
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done; goto done;
/* Copy xmlns attributes */
if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
#if 0 #if 0
/* If it is key I dont want to mark it */ /* If it is key I dont want to mark it */
if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0) if ((iamkey=yang_key_match(y0->yn_parent, x1name)) < 0)
@ -636,7 +662,7 @@ text_modify(cxobj *x0,
if (x0==NULL){ if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done; goto done;
goto ok; goto fail;
} }
case OP_REMOVE: /* fall thru */ case OP_REMOVE: /* fall thru */
if (x0){ if (x0){
@ -653,7 +679,7 @@ text_modify(cxobj *x0,
if (x0){ if (x0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done; goto done;
goto ok; goto fail;
} }
case OP_REPLACE: /* fall thru */ case OP_REPLACE: /* fall thru */
if (x0){ if (x0){
@ -679,11 +705,19 @@ text_modify(cxobj *x0,
if (x0==NULL){ if (x0==NULL){
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL) if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done; goto done;
/* Copy xmlns attributes */
if ((x1a = xml_find_type(x1, NULL, "xmlns", CX_ATTR)) != 0){
if ((x0a = xml_dup(x1a)) == NULL)
goto done;
if (xml_addsub(x0, x0a) < 0)
goto done;
}
if (op==OP_NONE) if (op==OP_NONE)
xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */ xml_flag_set(x0, XML_FLAG_NONE); /* Mark for potential deletion */
} }
/* First pass: mark existing children in base */ /* First pass: Loop through children of the x1 modification tree
/* Loop through children of the modification tree */ * collect matching nodes from x0 in x0vec (no changes to x0 children)
*/
if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){ if ((x0vec = calloc(xml_child_nr(x1), sizeof(x1))) == NULL){
clicon_err(OE_UNIX, errno, "calloc"); clicon_err(OE_UNIX, errno, "calloc");
goto done; goto done;
@ -699,28 +733,40 @@ text_modify(cxobj *x0,
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
x0c = NULL; x0c = NULL;
if (match_base_child(x0, x1c, &x0c, yc) < 0) if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
x0vec[i++] = x0c; #if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
} }
/* Second pass: modify tree */ #endif
x0vec[i++] = x0c; /* != NULL if x0c is matching x1c */
}
/* Second pass: Loop through children of the x1 modification tree again
* Now potentially modify x0:s children
* Here x0vec contains one-to-one matching nodes of x1:s children.
*/
x1c = NULL; x1c = NULL;
i = 0; i = 0;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
x1cname = xml_name(x1c); x1cname = xml_name(x1c);
x0c = x0vec[i++];
yc = yang_find_datanode(y0, x1cname); yc = yang_find_datanode(y0, x1cname);
if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done; goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */ /* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret)) if (ret == 0)
goto ok; goto fail;
} }
break; break;
case OP_DELETE: case OP_DELETE:
if (x0==NULL){ if (x0==NULL){
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done; goto done;
goto ok; goto fail;
} }
case OP_REMOVE: /* fall thru */ case OP_REMOVE: /* fall thru */
if (x0) if (x0)
@ -731,20 +777,26 @@ text_modify(cxobj *x0,
} /* CONTAINER switch op */ } /* CONTAINER switch op */
} /* else Y_CONTAINER */ } /* else Y_CONTAINER */
xml_sort(x0p, NULL); xml_sort(x0p, NULL);
ok: retval = 1;
retval = 0;
done: done:
if (x0vec) if (x0vec)
free(x0vec); free(x0vec);
return retval; return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify */ } /* text_modify */
/*! Modify a top-level base tree x0 with modification tree x1 /*! Modify a top-level base tree x0 with modification tree x1
* @param[in] th text handle
* @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x0 Base xml tree (can be NULL in add scenarios)
* @param[in] x1 xml tree which modifies base * @param[in] x1 xml tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] yspec Top-level yang spec (if y is NULL)
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[out] cbret Initialized cligen buffer. Contains return XML or "". * @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
* @retval 1 OK
* @see text_modify * @see text_modify
*/ */
static int static int
@ -762,6 +814,7 @@ text_modify_top(struct text_handle *th,
yang_stmt *yc; /* yang child */ yang_stmt *yc; /* yang child */
yang_stmt *ymod;/* yang module */ yang_stmt *ymod;/* yang module */
char *opstr; char *opstr;
int ret;
/* Assure top-levels are 'config' */ /* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0); assert(x0 && strcmp(xml_name(x0),"config")==0);
@ -790,7 +843,7 @@ text_modify_top(struct text_handle *th,
case OP_DELETE: case OP_DELETE:
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done; goto done;
goto ok; goto fail;
break; break;
default: default:
break; break;
@ -812,35 +865,42 @@ text_modify_top(struct text_handle *th,
goto done; goto done;
if (ymod != NULL) if (ymod != NULL)
yc = yang_find_datanode((yang_node*)ymod, x1cname); yc = yang_find_datanode((yang_node*)ymod, x1cname);
if (yc == NULL && _CLICON_XML_NS_ITERATE){ if (yc == NULL && !_CLICON_XML_NS_STRICT){
int i; if (xml_yang_find_non_strict(x1c, yspec, &yc) < 0)
for (i=0; i<yspec->yp_len; i++){ goto done;
ymod = yspec->yp_stmt[i];
if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL)
break;
}
} }
if (yc == NULL){ if (yc == NULL){
if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) if (netconf_unknown_element(cbret, "application", x1cname, "Unassigned yang spec") < 0)
goto done; goto done;
goto ok; goto fail;
} }
/* See if there is a corresponding node in the base tree */ /* See if there is a corresponding node in the base tree */
if (match_base_child(x0, x1c, &x0c, yc) < 0) if (match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) #if 1
if (x0c && (yc != xml_spec(x0c))){
/* There is a match but is should be replaced (choice)*/
if (xml_purge(x0c) < 0)
goto done;
x0c = NULL;
}
#endif
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
goto done; goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */ /* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret)) if (ret == 0)
goto ok; goto fail;
} }
ok: // ok:
retval = 0; retval = 1;
done: done:
return retval; return retval;
fail: /* cbret set */
retval = 0;
goto done;
} /* text_modify_top */ } /* text_modify_top */
/*! For containers without presence and no children, remove /*! For containers without presence and no children(except attrs), remove
* @param[in] x XML tree node * @param[in] x XML tree node
* See section 7.5.1 in rfc6020bis-02.txt: * See section 7.5.1 in rfc6020bis-02.txt:
* No presence: * No presence:
@ -869,7 +929,7 @@ xml_container_presence(cxobj *x,
} }
/* Mark node that is: container, have no children, dont have presence */ /* Mark node that is: container, have no children, dont have presence */
if (y->ys_keyword == Y_CONTAINER && if (y->ys_keyword == Y_CONTAINER &&
xml_child_nr(x)==0 && xml_child_nr_notype(x, CX_ATTR)==0 &&
yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL) yang_find((yang_node*)y, Y_PRESENCE, NULL) == NULL)
xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */ xml_flag_set(x, XML_FLAG_MARK); /* Mark, remove later */
retval = 0; retval = 0;
@ -897,15 +957,12 @@ text_put(xmldb_handle xh,
yang_spec *yspec; yang_spec *yspec;
cxobj *x0 = NULL; cxobj *x0 = NULL;
struct db_element *de = NULL; struct db_element *de = NULL;
int cbretlocal = 0; /* Set if cbret is NULL on entry */ int ret;
if (cbret == NULL){ if (cbret == NULL){
if ((cbret = cbuf_new()) == NULL){ clicon_err(OE_XML, EINVAL, "cbret is NULL");
clicon_err(OE_XML, errno, "cbuf_new");
goto done; goto done;
} }
cbretlocal++;
}
if ((yspec = th->th_yangspec) == NULL){ if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec"); clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done; goto done;
@ -957,25 +1014,19 @@ text_put(xmldb_handle xh,
xml_name(x0)); xml_name(x0));
goto done; goto done;
} }
#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 0 /* debug */
if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
#endif #endif
/* /*
* Modify base tree x with modification x1. This is where the * Modify base tree x with modification x1. This is where the
* new tree is made. * new tree is made.
*/ */
if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0) if ((ret = text_modify_top(th, x0, x1, yspec, op, cbret)) < 0)
goto done; goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */ /* If xml return - ie netconf error xml tree, then stop and return OK */
if (cbuf_len(cbret)) if (ret == 0)
goto ok; goto fail;
/* Remove NONE nodes if all subs recursively are also NONE */ /* Remove NONE nodes if all subs recursively are also NONE */
if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0)
@ -990,7 +1041,7 @@ text_put(xmldb_handle xh,
if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0) if (xml_tree_prune_flagged(x0, XML_FLAG_MARK, 1) < 0)
goto done; goto done;
#if 0 /* debug */ #if 0 /* debug */
if (xml_child_sort && xml_apply0(x0, -1, xml_sort_verify, NULL) < 0) if (xml_apply0(x0, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__); clicon_log(LOG_NOTICE, "%s: verify failed #3", __FUNCTION__);
#endif #endif
/* Write back to datastore cache if first time */ /* Write back to datastore cache if first time */
@ -1025,11 +1076,8 @@ text_put(xmldb_handle xh,
} }
else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0)
goto done; goto done;
ok: retval = 1;
retval = 0;
done: done:
if (cbretlocal && cbret)
cbuf_free(cbret);
if (f != NULL) if (f != NULL)
fclose(f); fclose(f);
if (dbfile) if (dbfile)
@ -1041,6 +1089,9 @@ text_put(xmldb_handle xh,
if (!th->th_cache && x0) if (!th->th_cache && x0)
xml_free(x0); xml_free(x0);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Copy database from db1 to db2 /*! Copy database from db1 to db2
@ -1386,6 +1437,8 @@ main(int argc,
char *yangmod; /* yang file */ char *yangmod; /* yang file */
yang_spec *yspec = NULL; yang_spec *yspec = NULL;
clicon_handle h; clicon_handle h;
cbuf *cbret = NULL;
int ret;
if ((h = clicon_handle_init()) == NULL) if ((h = clicon_handle_init()) == NULL)
goto done; goto done;
@ -1400,7 +1453,7 @@ main(int argc,
db_init(db); db_init(db);
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done goto done
if (yang_parse(h, NULL, yangmod, NULL, yspec) < 0) if (yang_spec_parse_module(h, yangmod, NULL, yspec) < 0)
goto done; goto done;
if (strcmp(cmd, "get")==0){ if (strcmp(cmd, "get")==0){
if (argc < 5) if (argc < 5)
@ -1434,13 +1487,21 @@ main(int argc,
op = OP_REMOVE; op = OP_REMOVE;
else else
usage(argv[0]); usage(argv[0]);
if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) if ((cbret = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
} }
if ((ret = xmldb_put(h, db, op, NULL, xn, cbret)) < 0)
goto done;
if (ret == 0)
fprintf(stderr, "%s\n", cbuf_get(cbret));
}
else else
usage(argv[0]); usage(argv[0]);
printf("\n"); printf("\n");
done: done:
if (cbret)
cbuf_free(cbret);
return 0; return 0;
} }

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -42,9 +42,9 @@ The example:
sudo make install sudo make install
``` ```
## How do you run Clixon example commands? ## How do I run Clixon example commands?
- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml` - Start a backend server: `clixon_backend -F -s init -f /usr/local/etc/example.xml`
- Start a cli session: `clixon_cli -f /usr/local/etc/example.xml` - Start a cli session: `clixon_cli -f /usr/local/etc/example.xml`
- Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml` - Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml`
- Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data` - Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data`
@ -72,6 +72,82 @@ grep clicon /etc/group
clicon:x:1001:<user>,www-data clicon:x:1001:<user>,www-data
``` ```
## How do I use the CLI?
The easiest way to use Clixon is via the CLI. Once the backend is started
Example:
```
clixon_cli -f /usr/local/etc/example.xml
cli> set interfaces interface eth9 ?
description enabled ipv4
ipv6 link-up-down-trap-enable type
cli> set interfaces interface eth9 type ex:eth
cli> validate
cli> commit
cli> show configuration xml
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth9</name>
<type>ex:eth</type>
<enabled>true</enabled>
</interface>
</interfaces>
cli> delete interfaces interface eth9
```
## How do I use netconf?
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
```
clixon_netconf -qf /usr/local/etc/example.xml
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc-reply><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth9</name><type>ex:eth</type><enabled>true</enabled></interface></interfaces></data></rpc-reply>]]>]]>
```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
```
Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml
```
and then invoke it from a client using
```
ssh -s <host> netconf
```
## How do I use restconf?
You can access clixon via REST API using restconf, such as using
curl. GET, PUT, POST are supported.
You need a web-server, such as nginx, and start a restconf fcgi
daemon, clixon_restconf.
For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default:
```
server {
...
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
}
```
Start nginx daemon
```
sudo /etc/init.d/nginx start
```
Example:
```
curl -G http://127.0.0.1/restconf/data/ietf-interfaces:interfaces/interface=eth9/type
[
{
"ietf-interfaces:type": "ex:eth"
}
]
```
Read more in the (restconf)[../apps/restconf] docs.
## What about reference documentation? ## What about reference documentation?
Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation.
You need to install doxygen and graphviz on your system. You need to install doxygen and graphviz on your system.
@ -121,7 +197,7 @@ are included.
The following configuration file options control the loading of Yang files: The following configuration file options control the loading of Yang files:
- `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules. - `CLICON_YANG_DIR` - A list of directories (yang dir path) where Clixon searches for module and submodules.
- `CLICON_YANG_MAIN_FILE` - Load a specific Yang module fiven by a file. - `CLICON_YANG_MAIN_FILE` - Load a specific Yang module given by a file.
- `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path. - `CLICON_YANG_MODULE_MAIN` - Specifies a single module to load. The module is searched for in the yang dir path.
- `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module. - `CLICON_YANG_MODULE_REVISION` : Specifies a revision to the main module.
- `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory. - `CLICON_YANG_MAIN_DIR` - Load all yang modules in this directory.
@ -161,59 +237,6 @@ sudo docker run -td olofhagsand/clixon_example
``` ```
Look in the example documentation for more info. Look in the example documentation for more info.
## How do I use netconf?
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
```
echo "<rpc><get-config><source><candidate/></source><configuration/></get-config></rpc>]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml
```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
```
Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml
```
and then invoke it from a client using
```
ssh -s <host> netconf
```
## How do I use restconf?
You can access clixon via REST API using restconf, such as using
curl. GET, PUT, POST are supported.
You need a web-server, such as nginx, and start a restconf fcgi
daemon, clixon_restconf.
For example, using nginx, install, and edit config file: /etc/nginx/sites-available/default:
```
server {
...
location /restconf {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
}
```
Start nginx daemon
```
sudo /etc/init.d/nginx start
```
Example:
```
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
[
{
"type": "eth"
}
]
```
Read more in the (restconf)[../apps/restconf] docs.
## Does Clixon support event streams? ## Does Clixon support event streams?
Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s. Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s.
@ -233,7 +256,7 @@ severity major;
or via NETCONF: or via NETCONF:
``` ```
clixon_netconf -qf /usr/local/etc/example.xml clixon_netconf -qf /usr/local/etc/example.xml
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]> <rpc><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]> <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
... ...
@ -242,11 +265,11 @@ or via restconf:
``` ```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
``` ```
Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io]. Consult [clixon restconf](../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as [Nginx Nchan](https://nchan.io).
## How should I start the backend daemon? ## How should I start the backend daemon?
There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you startthe daemon and how loading the configuration affects it: There are four different backend startup modes. There is differences in running state treatment, ie what state the machine is when you start the daemon and how loading the configuration affects it:
- none - Do not touch running state. Typically after crash when running state and db are synched. - none - Do not touch running state. Typically after crash when running state and db are synched.
- init - Initialize running state. Start with a completely clean running state. - init - Initialize running state. Start with a completely clean running state.
- running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists. - running - Commit running db configuration into running state. Typically after reboot if a persistent running db exists.
@ -277,7 +300,7 @@ There are two ways to add extra XML to running database after start. Note that
The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag): The first way is via a file. Assume you want to add this xml (the config tag is a necessary top-level tag):
``` ```
<config> <config>
<x>extra</x> <x xmlns="urn:example:clixon">extra</x>
</config> </config>
``` ```
You add this via the -c option: You add this via the -c option:
@ -289,12 +312,13 @@ The second way is by programming the plugin_reset() in the backend
plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c).
## I want to program. How do I extend the example? ## I want to program. How do I extend the example?
See [../apps/example] See [../apps/example](../apps/example)
- example.xml - Change the configuration file - example.xml - Change the configuration file
- The yang specifications - This is the central part. It changes the XML, database and the config cli. - The yang specifications - This is the central part. It changes the XML, database and the config cli.
- example_cli.cli - Change the fixed part of the CLI commands - example_cli.cli - Change the fixed part of the CLI commands
- example_cli.c - Cli C-commands are placed here. - example_cli.c - Cli C-commands are placed here.
- example_backend.c - Commit and validate functions. - example_backend.c - Commit and validate functions.
- example_backend_nacm.c - Secondary example plugin (for authorization)
- example_netconf.c - Netconf plugin - example_netconf.c - Netconf plugin
- example_restconf.c - Add restconf authentication, etc. - example_restconf.c - Add restconf authentication, etc.

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,19 +1,33 @@
# Clixon example # Clixon example
* [Content](#content)
* [Compile and run](#compile)
* [Using the CLI](#using-the-cli)
* [Using netconf](#using-netconf)
* [Streams](#streams)
* [RPC Operations](#rpc-operations)
* [State data](#state-data)
* [Authentication and NACM](#authentication-and-nacm)
* [Systemd](#systemd)
* [Docker](#docker)
* [Plugins](#plugins)
## Content
This directory contains a Clixon example which includes a simple example. It contains the following files: This directory contains a Clixon example which includes a simple example. It contains the following files:
* example.xml The configuration file. See yang/clixon-config@<date>.yang for all available fields. * `example.xml` The configuration file. See (yang/clixon-config@<date>.yang)[../yang/clixon-config@2018-10-21.yang] for the documentation of all available fields.
* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. * `example.yang` The yang spec of the example. It mainly includes ietf routing and IP modules.
* example_cli.cli CLIgen specification. * `example_cli.cli` CLIgen specification.
* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). * `example_cli.c` CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`).
* example_backend.c Backend callback plugin including example of: * `example_backend.c` Backend callback plugin including example of:
* transaction callbacks (validate/commit), * transaction callbacks (validate/commit),
* notification, * notification,
* rpc handler * rpc handler
* state-data handler, ie non-config data * state-data handler, ie non-config data
* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. * `example_backend_nacm.c` Secondary backend plugin. Plugins are loaded alphabetically.
* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback * `example_restconf.c` Restconf callback plugin containing an HTTP basic authentication callback
* example_netconf.c Netconf callback plugin * `example_netconf.c` Netconf callback plugin
* Makefile.in Example makefile where plugins are built and installed * `Makefile.in` Example makefile where plugins are built and installed
## Compile and run ## Compile and run
@ -47,10 +61,37 @@ Send restconf command
curl -G http://127.0.0.1/restconf/data curl -G http://127.0.0.1/restconf/data
``` ```
## Setting data example using netconf ## Using the CLI
The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code.
There are also many other commands available as examples. View the source file (example_cli.cli)[example_cli.cli] for more details.
The following example shows how to add an interface in candidate, validate and commit it to running, then look at it (as xml) and finally delete it.
```
clixon_cli -f /usr/local/etc/example.xml
cli> set interfaces interface eth9 ?
description enabled ipv4
ipv6 link-up-down-trap-enable type
cli> set interfaces interface eth9 type ex:eth
cli> validate
cli> commit
cli> show configuration xml
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>eth9</name>
<type>ex:eth</type>
<enabled>true</enabled>
</interface>
</interfaces>
cli> delete interfaces interface eth9
```
## Using Netconf
The following example shows how to set data using netconf:
``` ```
<rpc><edit-config><target><candidate/></target><config> <rpc><edit-config><target><candidate/></target><config>
<interfaces> <interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface> <interface>
<name>eth1</name> <name>eth1</name>
<enabled>true</enabled> <enabled>true</enabled>
@ -65,13 +106,13 @@ Send restconf command
</config></edit-config></rpc>]]>]]> </config></edit-config></rpc>]]>]]>
``` ```
## Getting data using netconf ### Getting data using netconf
``` ```
<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]> <rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter/></get-config></rpc>]]>]]> <rpc><get-config><source><candidate/></source><filter/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath"/></get-config></rpc>]]>]]> <rpc><get-config><source><candidate/></source><filter type="xpath"/></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="subtree"><configuration><interfaces><interface><ipv4/></interface></interfaces></configuration></filter></get-config></rpc>]]>]]> <rpc><get-config><source><candidate/></source><filter type="subtree"><data><interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces"><interface><name>eth9</name><type>ex:eth</type></interface></interfaces></data></filter></get-config></rpc>]]>]]>
<rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface/ipv4"/></get-config></rpc>]]>]]> <rpc><get-config><source><candidate/></source><filter type="xpath" select="/interfaces/interface"/></get-config></rpc>]]>]]>
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]> <rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
``` ```
@ -80,23 +121,134 @@ Send restconf command
The example has an EXAMPLE stream notification triggering every 5s. To start a notification The example has an EXAMPLE stream notification triggering every 5s. To start a notification
stream in the session using netconf, create a subscription: stream in the session using netconf, create a subscription:
``` ```
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]> <rpc><create-subscription xmlns="urn:ietf:params:xml:ns:netmod:notification"><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]> <rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]> <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2019-01-02T10:20:05.929272</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
... ...
``` ```
This can also be triggered via the CLI: This can also be triggered via the CLI:
``` ```
clixon_cli -f /usr/local/etc/example.xml
cli> notify cli> notify
cli> Routing notification cli> event-class fault;
Routing notification reportingEntity {
card Ethernet0;
}
severity major;
... ...
cli> no notify
cli>
``` ```
Restconf support is also supported, see [../apps/restconf/README.md]. Restconf support is also supported, see (restc)[../apps/restconf/README.md].
## Initializing a plugin
## RPC Operations
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example using CLI:
```
cli> rpc ipv4
rpc-reply {
route {
address-family ipv4;
next-hop {
next-hop-list 2.3.4.5;
}
source-protocol static;
}
}
```
Netconf:
```
<rpc><fib-route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><routing-instance-name>ipv4</routing-instance-name></fib-route></rpc>]]>]]>
<rpc-reply><route xmlns="urn:ietf:params:xml:ns:yang:ietf-routing"><address-family>ipv4</address-family><next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop><source-protocol>static</source-protocol></route></rpc-reply>]]>]]>
```
Restconf:
```
curl -X POST http://localhost/restconf/operations/ietf-routing:fib-route -d '{"ietf-routing:input":{"routing-instance-name":"ipv4"}}'
```
### Details
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function in [example_cli.c](example_cli.c)).
In the (example_backend.c)[example_backend.c], a callback is registered (fib_route()) which handles the RPC (this is just dummy data):
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
"</route></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data(not only configuration data).
In YANG state data is specified with `config false;`. In the example,
`state` is state data, see (example.yang)[example.yang]
To return state data, you need to write a backend state data callback
with the name "plugin_statedata" where you return an XML tree with
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
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: 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
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`
## Plugins
The example includes a restonf, netconf, CLI and two backend plugins. The example includes a restonf, netconf, CLI and two backend plugins.
Each plugin is initiated with an API struct followed by a plugin init function. Each plugin is initiated with an API struct followed by a plugin init function.
@ -127,96 +279,3 @@ clixon_plugin_init(clicon_handle h)
return &api; /* Return NULL on error */ return &api; /* Return NULL on error */
} }
``` ```
## Operation data
Clixon implements Yang RPC operations by an extension mechanism. The
extension mechanism enables you to add application-specific
operations. It works by adding user-defined callbacks for added
netconf operations. It is possible to use the extension mechanism
independent of the yang rpc construct, but it is recommended. The example includes an example:
Example:
```
cli> rpc ipv4
<rpc-reply>
<ok/>
</rpc-reply>
```
The example works by creating a netconf rpc call and sending it to the backend: (see the fib_route_rpc() function).
```
<rpc>
<fib-route>
<routing-instance-name>ipv4</routing-instance-name>
</fib-route>
</rpc>
```
In the backend, a callback is registered (fib_route()) which handles the RPC.
```
static int
fib_route(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* Client session */
void *regarg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
return 0;
}
int
clixon_plugin_init(clicon_handle h)
{
...
rpc_callback_register(h, fib_route, NULL, "fib-route");
...
}
```
## State data
Netconf <get> and restconf GET also returns state data, in contrast to
config data.
p
In YANG state data is specified with "config false;". In the example, interface-state is state data.
To return state data, you need to write a backend state data callback
with the name "plugin_statedata" where you return an XML tree with
state. This is then merged with config data by the system.
A static example of returning state data is in the example. Note that
a real example would poll or get the interface counters via a system
call, as well as use the "xpath" argument to identify the requested
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: 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.
## Docker
Run the example as a docker container and access it from a host CLI as follows:
```
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
```
make docker
make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`

View file

@ -26,6 +26,10 @@ module example {
} }
/* Translation function example - See also example_cli */ /* Translation function example - See also example_cli */
list translate{ list translate{
key k;
leaf k{
type string;
}
leaf value{ leaf value{
type string; type string;
} }
@ -73,13 +77,55 @@ module example {
} }
} }
} }
rpc debug { rpc empty {
description "Set debug level of backend. XXX should be in clixon-config"; description "Smallest possible RPC with no input or output";
}
rpc example {
description "Some example input/output for testing RFC7950 7.14.
RPC simply echoes the input for debugging.";
input { input {
leaf level { leaf x {
type uint32; description
"If a leaf in the input tree has a 'mandatory' statement with
the value 'true', the leaf MUST be present in an RPC invocation.";
type string;
mandatory true;
}
leaf y {
description
"If a leaf in the input tree has a 'mandatory' statement with the
value 'true', the leaf MUST be present in an RPC invocation.";
type string;
default "42";
}
leaf-list z {
description
"If a leaf-list in the input tree has one or more default
values, the server MUST use these values (XXX not supported)";
type string;
}
leaf w {
description
"If any node has a 'when' statement that would evaluate to
'false',then this node MUST NOT be present in the input tree.
(XXX not supported)";
type string;
when "/translate/k=5/value='w'";
}
}
output {
leaf x {
type string;
}
leaf y {
type string;
}
leaf z {
type string;
}
leaf w {
type string;
} }
} }
} }
} }

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -96,7 +96,7 @@ example_stream_timer(int fd,
int retval = -1; int retval = -1;
clicon_handle h = (clicon_handle)arg; clicon_handle h = (clicon_handle)arg;
/* XXX Change to actual netconf notifications */ /* XXX Change to actual netconf notifications and namespace */
if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0) if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done; goto done;
if (example_stream_timer_setup(h) < 0) if (example_stream_timer_setup(h) < 0)
@ -129,9 +129,10 @@ fib_route(clicon_handle h, /* Clicon handle */
void *arg, /* Client session */ void *arg, /* Client session */
void *regarg) /* Argument given at register */ void *regarg) /* Argument given at register */
{ {
cprintf(cbret, "<rpc-reply><route>" cprintf(cbret, "<rpc-reply><route xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">"
"<address-family>ipv4</address-family>" "<address-family>ipv4</address-family>"
"<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>" "<next-hop><next-hop-list>2.3.4.5</next-hop-list></next-hop>"
"<source-protocol>static</source-protocol>"
"</route></rpc-reply>"); "</route></rpc-reply>");
return 0; return 0;
} }
@ -146,7 +147,7 @@ route_count(clicon_handle h,
void *arg, void *arg,
void *regarg) /* Argument given at register */ void *regarg) /* Argument given at register */
{ {
cprintf(cbret, "<rpc-reply><number-of-routes>42</number-of-routes></rpc-reply>"); cprintf(cbret, "<rpc-reply><number-of-routes xmlns=\"urn:ietf:params:xml:ns:yang:ietf-routing\">42</number-of-routes></rpc-reply>");
return 0; return 0;
} }
@ -157,7 +158,7 @@ route_count(clicon_handle h,
* in [RFC6241]. * in [RFC6241].
*/ */
static int static int
empty(clicon_handle h, /* Clicon handle */ empty_rpc(clicon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */ cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */ cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* client_entry */ void *arg, /* client_entry */
@ -167,6 +168,39 @@ empty(clicon_handle h, /* Clicon handle */
return 0; return 0;
} }
/*! More elaborate example RPC for testing
* The RPC returns the incoming parameters
*/
static int
example_rpc(clicon_handle h, /* Clicon handle */
cxobj *xe, /* Request: <rpc><xn></rpc> */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg, /* client_entry */
void *regarg) /* Argument given at register */
{
int retval = -1;
cxobj *x = NULL;
char *namespace;
/* get namespace from rpc name, return back in each output parameter */
if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){
clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe));
goto done;
}
cprintf(cbret, "<rpc-reply>");
while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
if (clicon_xml2cbuf(cbret, x, 0, 0) < 0)
goto done;
}
cprintf(cbret, "</rpc-reply>");
retval = 0;
done:
return retval;
}
/*! Called to get state data from plugin /*! Called to get state data from plugin
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] xpath String with XPATH syntax. or NULL for all
@ -196,7 +230,7 @@ example_statedata(clicon_handle h,
* Note this state needs to be accomanied by yang snippet * Note this state needs to be accomanied by yang snippet
* above * above
*/ */
if (xml_parse_string("<state>" if (xml_parse_string("<state xmlns=\"urn:example:clixon\">"
"<op>42</op>" "<op>42</op>"
"</state>", NULL, &xstate) < 0) "</state>", NULL, &xstate) < 0)
goto done; goto done;
@ -225,19 +259,32 @@ example_reset(clicon_handle h,
{ {
int retval = -1; int retval = -1;
cxobj *xt = NULL; cxobj *xt = NULL;
int ret;
cbuf *cbret = NULL;
if (xml_parse_string("<config><interfaces><interface>" if (xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\"><interface>"
"<name>lo</name><type>ex:loopback</type>" "<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0) "</interface></interfaces></config>", NULL, &xt) < 0)
goto done; goto done;
/* Replace parent w fiorst child */ /* Replace parent w first child */
if (xml_rootchild(xt, 0, &xt) < 0) if (xml_rootchild(xt, 0, &xt) < 0)
goto done; goto done;
/* Merge user reset state */ if ((cbret = cbuf_new()) == NULL){
if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) clicon_err(OE_UNIX, errno, "cbuf_new");
goto done; goto done;
}
/* Merge user reset state */
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
cbuf_get(cbret));
goto done;
}
retval = 0; retval = 0;
done: done:
if (cbret)
cbuf_free(cbret);
if (xt != NULL) if (xt != NULL)
xml_free(xt); xml_free(xt);
return retval; return retval;
@ -315,22 +362,31 @@ clixon_plugin_init(clicon_handle h)
if (example_stream_timer_setup(h) < 0) if (example_stream_timer_setup(h) < 0)
goto done; goto done;
/* Register callback for routing rpc calls */ /* Register callback for routing rpc calls
*/
if (rpc_callback_register(h, fib_route, if (rpc_callback_register(h, fib_route,
NULL, NULL,
"fib-route"/* Xml tag when callback is made */ "fib-route"/* Xml tag when callback is made */
) < 0) ) < 0)
goto done; goto done;
/* From ietf-routing.yang */
if (rpc_callback_register(h, route_count, if (rpc_callback_register(h, route_count,
NULL, NULL,
"route-count"/* Xml tag when callback is made */ "route-count"/* Xml tag when callback is made */
) < 0) ) < 0)
goto done; goto done;
if (rpc_callback_register(h, empty, /* From example.yang (clicon) */
if (rpc_callback_register(h, empty_rpc,
NULL, NULL,
"empty"/* Xml tag when callback is made */ "empty"/* Xml tag when callback is made */
) < 0) ) < 0)
goto done; goto done;
if (rpc_callback_register(h, example_rpc,
NULL,
"example"/* Xml tag when callback is made */
) < 0)
goto done;
/* Return plugin API */ /* Return plugin API */
return &api; return &api;

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h,
/* User supplied variable in CLI command */ /* User supplied variable in CLI command */
instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */
/* Create XML for fib-route netconf RPC */ /* Create XML for fib-route netconf 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>", 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><destination-address><address-family>ipv4</address-family></destination-address></fib-route></rpc>",
clicon_username_get(h), clicon_username_get(h),
cv_string_get(instance)) < 0) cv_string_get(instance)) < 0)
goto done; goto done;
@ -110,7 +110,7 @@ fib_route_rpc(clicon_handle h,
goto done; goto done;
} }
/* Print result */ /* Print result */
xml_print(stdout, xml_child_i(xret, 0)); xml2txt(stdout, xml_child_i(xret, 0), 0);
retval = 0; retval = 0;
done: done:
if (xret) if (xret)

View file

@ -24,7 +24,7 @@ debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
} }
copy("Copy and create a new object") { copy("Copy and create a new object") {
interface("Copy interface"){ interface("Copy interface"){
<name:string expand_dbvar("candidate","/interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname"); <name:string expand_dbvar("candidate","/ietf-interfaces:interfaces/interface=%s/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s='%s']","name","name","toname");
} }
} }
discard("Discard edits (rollback 0)"), discard_changes(); discard("Discard edits (rollback 0)"), discard_changes();

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -71,7 +71,7 @@ int netconf_client_rpc(clicon_handle h,
void *regarg) void *regarg)
{ {
clicon_debug(1, "%s restconf", __FUNCTION__); clicon_debug(1, "%s restconf", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>"); cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0; return 0;
} }

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -275,7 +275,7 @@ restconf_client_rpc(clicon_handle h,
{ {
// FCGX_Request *r = (FCGX_Request *)arg; // FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
cprintf(cbret, "<rpc-reply><result>ok</result></rpc-reply>"); cprintf(cbret, "<rpc-reply><result xmlns=\"urn:example:clixon\">ok</result></rpc-reply>");
return 0; return 0;
} }

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
/* /*
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -51,11 +51,6 @@ int strverscmp (__const char *__s1, __const char *__s2);
*/ */
#define XMLNS_YANG_ONLY 1 #define XMLNS_YANG_ONLY 1
/* Set for full XML namespace code in XML, NETCONF and YANG
* Experimental
*/
#undef ENABLE_XMLNS
/* If set, patch all CLI spec calls to @datamodel:tree to @datamodel. /* If set, patch all CLI spec calls to @datamodel:tree to @datamodel.
* This is a backward compatible fix for 3.9 for CLIgen specification files * This is a backward compatible fix for 3.9 for CLIgen specification files
* using model generation (CLIXON_CLI_GENMODEL). * using model generation (CLIXON_CLI_GENMODEL).

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -1,7 +1,7 @@
# #
# ***** BEGIN LICENSE BLOCK ***** # ***** BEGIN LICENSE BLOCK *****
# #
# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren # Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
# #
# This file is part of CLIXON # This file is part of CLIXON
# #

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -43,6 +43,7 @@ int xml2json_cbuf(cbuf *cb, cxobj *x, int pretty);
int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty); int xml2json_cbuf_vec(cbuf *cb, cxobj **vec, size_t veclen, int pretty);
int xml2json(FILE *f, cxobj *x, int pretty); int xml2json(FILE *f, cxobj *x, int pretty);
int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty); int xml2json_vec(FILE *f, cxobj **vec, size_t veclen, int pretty);
int json2xml_ns(yang_spec *yspec, cxobj *x, cxobj **xerr);
int json_parse_str(char *str, cxobj **xt); int json_parse_str(char *str, cxobj **xt);
int json_parse_file(int fd, yang_spec *yspec, cxobj **xt); int json_parse_file(int fd, yang_spec *yspec, cxobj **xt);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -46,10 +46,14 @@ int netconf_too_big(cbuf *cb, char *type, char *message);
int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message);
int netconf_missing_element(cbuf *cb, char *type, char *info, char *message); int netconf_missing_element(cbuf *cb, char *type, char *element, char *message);
int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); int netconf_missing_element_xml(cxobj **xret, char *type, char *element, char *message);
int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); int netconf_bad_element(cbuf *cb, char *type, char *info, char *element);
int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_bad_element_xml(cxobj **xret, char *type, char *info, char *element);
int netconf_unknown_element(cbuf *cb, char *type, char *element, char *message);
int netconf_unknown_element_xml(cxobj **xret, char *type, char *element, char *message);
int netconf_unknown_namespace(cbuf *cb, char *type, char *namespace, char *message);
int netconf_unknown_namespace_xml(cxobj **xret, char *type, char *namespace, char *message);
int netconf_access_denied(cbuf *cb, char *type, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message);
int netconf_access_denied_xml(cxobj **xret, char *type, char *message); int netconf_access_denied_xml(cxobj **xret, char *type, char *message);
int netconf_lock_denied(cbuf *cb, char *info, char *message); int netconf_lock_denied(cbuf *cb, char *info, char *message);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -80,6 +80,10 @@ enum startup_mode_t{
/* Print registry on file. For debugging. */ /* Print registry on file. For debugging. */
void clicon_option_dump(clicon_handle h, int dblevel); void clicon_option_dump(clicon_handle h, int dblevel);
/* Add a clicon options overriding file setting */
int clicon_option_add(clicon_handle h, char *name, char *value);
/* Initialize options: set defaults, read config-file, etc */ /* Initialize options: set defaults, read config-file, etc */
int clicon_options_main(clicon_handle h, yang_spec *yspec); int clicon_options_main(clicon_handle h, yang_spec *yspec);
@ -174,10 +178,8 @@ int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
cxobj * clicon_nacm_ext(clicon_handle h); cxobj * clicon_nacm_ext(clicon_handle h);
int clicon_nacm_ext_set(clicon_handle h, cxobj *xn); int clicon_nacm_ext_set(clicon_handle h, cxobj *xn);
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
yang_spec * clicon_config_yang(clicon_handle h); yang_spec * clicon_config_yang(clicon_handle h);
int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys); int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys);
#endif
cxobj *clicon_conf_xml(clicon_handle h); cxobj *clicon_conf_xml(clicon_handle h);
int clicon_conf_xml_set(clicon_handle h, cxobj *x); int clicon_conf_xml_set(clicon_handle h, cxobj *x);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.
@ -66,7 +66,7 @@ struct clicon_msg *clicon_msg_encode(char *format, ...) __attribute__ ((format (
#else #else
struct clicon_msg *clicon_msg_encode(char *format, ...); struct clicon_msg *clicon_msg_encode(char *format, ...);
#endif #endif
int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml); int clicon_msg_decode(struct clicon_msg *msg, yang_spec *yspec, cxobj **xml);
int clicon_connect_unix(char *sockpath); int clicon_connect_unix(char *sockpath);

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

View file

@ -2,7 +2,7 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
This file is part of CLIXON. This file is part of CLIXON.

Some files were not shown because too many files have changed in this diff Show more