diff --git a/.gitignore b/.gitignore index e9ed0e3e..9aaadb67 100644 --- a/.gitignore +++ b/.gitignore @@ -12,12 +12,14 @@ etc/Makefile example/Makefile lib/Makefile lib/*/Makefile +test/Makefile +yang/Makefile +yang/*/Makefile autom4te.cache/ -clixon.conf.cpp -clixon.mk config.log config.status +TAGS apps/backend/clixon_backend apps/backend/test @@ -44,3 +46,6 @@ lib/clixon/clixon.h build-root/*.tar.xz build-root/*.rpm build-root/rpmbuild + +test/site.sh +doc/html \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..b8c1a54e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: c +# safelist +branches: + only: + - master + - develop +before_script: + - sudo apt-get install -y libfcgi-dev + - ./test/travis/before_script.sh + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fdfe034..a3bab479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,170 @@ # Clixon Changelog +## 3.9.0 (Preliminary Target: February 2019) + +### Major New features +1. Correct XML namespace handling + * According to [XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208) in restconf and Netconf. + * Remaining deviations from strict namespace handling: + * edit-config xpath select statement does not support namespaces + * notifications do not support namespaces. + * ietf-netconf base syntax is default `urn:ietf:params:xml:ns:netconf:base:1.0` and may not be explicitly given. However, in future versions this may be mandatory. + * CLI syntax (ie generated commands) do not have namespaces. + + * The following example shows changes in netconf and restconf: + * Wrong Netconf RPC: + ``` + + + + + + ipv4 + + + ``` + * Correct Netconf RPC: + ``` + # xmlns may be ommitted + + + + + ipv4 + + + ``` + * Example: Correct restconf request: + ``` + POST http://localhost/restconf/operations/example:example) + Content-Type: application/yang-data+json + { + "example:input":{ + "x":0 + } + } + ``` + * Example: correct Restconf 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] +1. Yang upgrade (RFC7950) + * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) + * See https://github.com/clicon/clixon/issues/84 + * RPC method input parameters validated + * see https://github.com/clicon/clixon/issues/47 + * Support of submodule, include and belongs-to. + * 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 + * 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 + * `CLICON_YANG_DIR` is changed from a single directory to a path of directories + * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list + * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. + * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. +1. NACM (RFC8341) + * Experimental support, no performance enhancements and need further testing + * Incoming RPC Message validation is supported (3.4.4) + * Data Node Access validation is supported (3.4.5), except: + * rule-type data-node path is not supported + * Outgoing noitification aithorization is _not_ supported (3.4.6) + * RPC:s are supported _except_: + * `copy-config`for other src/target combinations than running/startup (3.2.6) + * `commit` - NACM is applied to candidate and running operations only (3.2.8) + * Client-side RPC:s are _not_ supported. + * Recovery user "_nacm_recovery" added. + +### API changes on existing features (you may need to change your code) +* Added `username` argument to `xmldb_put()` datastore function for NACM data-node write checks +* Rearranged yang files + * Moved and updated all standard ietf and iana yang files from example and yang/ to `yang/standard`. + * Moved clixon yang files from yang to `yang/clixon` + * New configure option to disable standard yang files: `./configure --disable-stdyangs` + * This is to make it easier to use standard IETF/IANA yang files in separate directory + * Renamed example yang from example.yang -> clixon-example.yang +* clixon_cli -p (printspec) changed semantics to add new yang path dir (see minor changes). +* 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.yang` to `clixon-rfc5277.yang`. + * 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 + * Example: In `choice c{ mandatory true; leaf x; }`, `x` was not previously enforced but is now. +* 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. +* XML namespace handling is corrected (see major changes) + * 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. +* Add `/usr/local/share/clixon` 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 + * If you generate CLI code from the model (CLIXON_CLI_GENMODEL). + * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h + +### Minor changes +* Change GIT branch handling to a single working master branch + * Develop branched abandoned + * Travis CI supported, see [https://travis-ci.org/clicon/clixon] +* XML parser conformance to W3 spec + * Names lexically correct (NCName) + * Syntactically Correct handling of '=" command-line option to all programs: backend, cli, netconf, restconf. + * Any config option from file can be overrided by giving them on command-line. +* Added -p command-line option to all programs: backend, cli, netconf, restconf. + * -p adds a new dir to the yang path dir. (same as -o CLICON_YAN_DIR=) +* 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. This fixes the most acute problems, but 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()` and `xml_namespace_set()` to `xml_prefix()` and `xml_prefix_set()`, respectively. +* Changed all make tags --> make TAGS +* Keyvalue datastore removed (it has been disabled since 3.3.3) +* New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. + +### Corrected Bugs +* Partially corrected: [yang type range statement does not support multiple values](https://github.com/clicon/clixon/issues/59). + * Should work for netconf and restconf, but not for CLI. +* Fixed: [Range parsing is not RFC 7950 compliant](https://github.com/clicon/clixon/issues/71) +* xml_cmp() compares numeric nodes based on string value [https://github.com/clicon/clixon/issues/64] +* xml_cmp() respects 'ordered-by user' for state nodes, which violates RFC 7950 [https://github.com/clicon/clixon/issues/63]. (Thanks JDL) +* XML<>JSON conversion problems [https://github.com/clicon/clixon/issues/66] + * CDATA sections stripped from XML when converted to JSON +* Restconf returns error when RPC generates "ok" reply [https://github.com/clicon/clixon/issues/69] +* xsd regular expression support for character classes [https://github.com/clicon/clixon/issues/68] + * added support for \c, \d, \w, \W, \s, \S. +* Removing newlines from XML data [https://github.com/clicon/clixon/issues/65] +* Fixed [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) +* Netconf/Restconf RPC extra input arguments are ignored (https://github.com/clicon/clixon/issues/47) + ## 3.8.0 (6 Nov 2018) ### Major New features @@ -200,7 +365,7 @@ translate { ### Known issues * 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: ``` x:des3 ``` diff --git a/README_DEVELOP.md b/DEVELOP.md similarity index 83% rename from README_DEVELOP.md rename to DEVELOP.md index 350d3636..1755effa 100644 --- a/README_DEVELOP.md +++ b/DEVELOP.md @@ -4,6 +4,7 @@ * [How to work in git (branching)](#branching) * [How the meta-configure stuff works](#meta-configure) * [How to debug](#debug) + * [New release](#new-release) ## Documentation How to document the code @@ -30,10 +31,9 @@ How to document the code ## Branching How to work in git (branching) -Basically follows: http://nvie.com/posts/a-successful-git-branching-model/ -only somewhat simplified: +Try to keep a single master branch always working. Currently testing is made using [Travis CI](https://travis-ci.org/clicon/clixon). -Do commits in develop branch. When done, merge with master. +However, releases are made periodically (ca every 3 months) which is more tested. ## How the meta-configure stuff works ``` @@ -92,4 +92,14 @@ EOF valgrind --leak-check=full --show-leak-kinds=all clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang valgrind --tool=callgrind clixon_netconf -qf /tmp/myconf.xml -y /tmp/my.yang sudo kcachegrind - ``` \ No newline at end of file + ``` + +## 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 +* change CLIXON_VERSION in configure.ac +* git tag -a diff --git a/LICENSE.md b/LICENSE.md index 84997252..af683735 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2009-2018 Olof Hagsand and Benny Holmgren +Copyright 2009-2019 Olof Hagsand and Benny Holmgren CLIXON is dual license. diff --git a/Makefile.in b/Makefile.in index 3d34933a..b5a833b1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ # # ***** 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 # @@ -54,7 +54,7 @@ SHELL = /bin/sh SUBDIRS = lib apps include etc datastore util yang -.PHONY: doc all clean depend $(SUBDIRS) install loc TAGS .config.status docker +.PHONY: doc example all clean depend $(SUBDIRS) install loc TAGS .config.status docker test all: $(SUBDIRS) @@ -138,6 +138,12 @@ pkg-rpm: dist pkg-srpm: dist make -C extras/rpm srpm +example: + (cd $@ && $(MAKE) $(MFLAGS) all) + +test: #example + (cd $@ && $(MAKE) $(MFLAGS) all) + docker: for i in docker; \ do (cd $$i && $(MAKE) $(MFLAGS) $@); done diff --git a/README.md b/README.md index 9c52da1b..77ff632f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/clicon/clixon.png)](https://travis-ci.org/clicon/clixon) + # Clixon Clixon is a YANG-based configuration manager, with interactive CLI, @@ -5,7 +7,7 @@ NETCONF and RESTCONF interfaces, an embedded database and transaction support. * [Background](#background) - * [Frequently asked questions](doc/FAQ.md) + * [Frequently asked questions (FAQ)](doc/FAQ.md) * [Installation](#installation) * [Licenses](#licenses) * [Support](#support) @@ -24,15 +26,16 @@ support. * [Clixon project page](http://www.clicon.org) * [Tests](test/) * [Docker](docker/) - * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) + * [Roadmap](ROADMAP.md) + * [Reference manual](#reference) Background ========== Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture. -Users of clixon currently include: +Users of Clixon currently include: * [Netgate](https://www.netgate.com) * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) @@ -97,13 +100,33 @@ XML Clixon has its own implementation of XML and XPATH implementation. The standards covered include: -- [XML](https://www.w3.org/TR/2008/REC-xml-20081126) -- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208) -- [XPATH](https://www.w3.org/TR/xpath-10) +- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126) +- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208) +- [XPATH 1.0](https://www.w3.org/TR/xpath-10) + +Not supported: +- !DOCTYPE (ie DTD) + +Historically, Clixon has not until 3.9 made strict namespace +enforcing. For example, the following non-strict netconf was +previously accepted: +``` + +``` +In 3.9, the same statement should be, for example: +``` + +``` +Note that base netconf syntax is still not enforced but recommended: +``` + + + +``` Yang ==== -YANG and XML is at the heart of Clixon. Yang modules are used as a +YANG and XML is the heart of Clixon. Yang modules are used as a specification for handling XML configuration data. The YANG spec is used to generate an interactive CLI, netconf and restconf clients. It also manages an XML datastore. @@ -120,6 +143,12 @@ However, the following YANG syntax modules are not implemented: - action - belongs-to +Restrictions on Yang types are as follows: +- 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) +- default values on leaf-lists (RFC7950 7.7.2) + Netconf ======= Clixon implements the following NETCONF proposals or standards: @@ -128,7 +157,14 @@ Clixon implements the following NETCONF proposals or standards: - [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt) - [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt) -Clixon does not yet support the following netconf features: +The following RFC6241 capabilities/features are hardcoded in Clixon: +- :candidate (RFC6241 8.3) +- :validate (RFC6241 8.6) +- :startup (RFC6241 8.7) +- :xpath (RFC6241 8.9) +- :notification: (RFC5277) + +Clixon does not support the following netconf features: - :url capability - copy-config source config @@ -137,6 +173,9 @@ Clixon does not yet support the following netconf features: - edit-config config-text - edit-config operation +Some other deviations from the RFC: +- edit-config xpath select statement does not support namespaces + Restconf ======== Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to @@ -158,7 +197,7 @@ Datastore ========= The Clixon datastore is a stand-alone XML based datastore. The idea is to be able to use different datastores backends with the same -API. +API. Currently only an XML plain text datastore is supported. The datastore is primarily designed to be used by Clixon but can be used separately. @@ -175,7 +214,7 @@ subsystem can be used. Restconf however needs credentials. This is done by writing a credentials callback in a restconf plugin. See: * [FAQ](doc/FAQ.md#how-do-i-write-an-authentication-callback). * [Example](example/README.md) has an example how to do this with HTTP basic auth. - * I have done this for another project using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration) + * It has been done for other projects using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration) The clients send the ID of the user using a "username" attribute with the RPC calls to the backend. Note that the backend trusts the clients @@ -183,36 +222,28 @@ so the clients can in principle fake a username. NACM ==== -Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). It has limited functionality. +Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). -The support is as follows: +To enable NACM: -* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang) -* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping. +* The `CLICON_NACM_MODE` config variable is by default `disabled`. +* If the mode is internal`, NACM configurations are expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping. * If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted. -* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables. -* There are two [tests](test/README.md) using internal and external NACM config -* The backend provides a limited NACM support (when enabled) described below -NACM is implemented in the backend and a single access check is made -in `from_client_msg()` when an internal netconf RPC has -just been received and decoded. The code is in `nacm_access()`. +The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables. -The functionality is as follows: -* Notification is not supported -* Groups are supported -* Rule-lists are supported -* Rules are supported as follows - * module-name: Only '*' supported - * access-operations: only '*' and 'exec' supported - * rpc-name: fully supported (eg edit-config/get-config, etc) - * action: fully supported (permit/deny) - -The tests outlines an example of three groups (taken from the RFC): admin, limited and guest: -* admin: Full access -* limited: Read access (get and get-config) -* guest: No access +NACM is implemented in the backend with incoming RPC and data node access control points. +The functionality is as follows (references to sections in [RFC8341](https://tools.ietf.org/html/rfc8341)): +* Access control point support: + * Incoming RPC Message validation is supported (3.4.4) + * Data Node Access validation is supported (3.4.5), except: + * rule-type data-node path is not supported + * Outgoing noitification aithorization is _not_ supported (3.4.6) +* RPC:s are supported _except_: + * `copy-config`for other src/target combinations than running/startup (3.2.6) + * `commit` - NACM is applied to candidate and running operations only (3.2.8) +* Client-side RPC:s are _not_ supported. Runtime ======= @@ -221,3 +252,13 @@ Runtime The figure shows the SDK runtime of Clixon. +Reference +========= +Clixon uses [Doxygen](http://www.doxygen.nl/index.html) for reference documentation. +You need to install doxygen and graphviz on your system. +Build it in the doc directory and point the browser to `.../clixon/doc/html/index.html` as follows: +``` +> cd doc +> make doc +> make graphs # detailed callgraphs +``` \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index e0dd98f3..2b884845 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,27 +1,42 @@ # Clixon roadmap -Not in prio order (yet) +## High prio +- 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 +- (DONE) NACM (RFC 8341) + - NACM support for create, read, update, delete operations + - ACM support for specifying a module name other than '*' +- (DONE)XML [Namespace handling](https://github.com/clicon/clixon/issues/49) (DONE) -- XML - - [Namespace handling](https://github.com/clicon/clixon/issues/49) +## Medium prio: +- Support a plugin callback that is invoked when copy-config is called. +- Preserve CLI command history across sessions. The up/down arrows +- (DONE)Support for XML regex's. + - Currently Posix extended regular expressions +- (DONE) Input validation on custom RPCs/ + - [Sanity checks](https://github.com/clicon/clixon/issues/47) + +## 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 - Support for additional Netconf [edit-config modes](https://github.com/clicon/clixon/issues/53) - 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) -- Netconf backend (Clixon acts as netconf controller) - Restconf - Query parameters -- NACM (RFC 8341) is somewhat limited - - Extend with data node access (read/create/delete/update/execute) - Streams (netconf and restconf) - Extend native stream mode with external persistent timeseries database, eg influxdb. - Jenkins CI/CD and webhooks - YANG - - [Cardinality](https://github.com/clicon/clixon/issues/48) - RFC 6022 [NETCONF monitoring](https://github.com/clicon/clixon/issues/39) - - Factory default Setting - draft-wu-netconf-restconf-factory-restore-03 - - Deviation, belongs-to, min/max-elements, action, unique + - Deviation, min/max-elements, action, unique - Containers - [Docker improvements](https://github.com/clicon/clixon/issues/44) - Kubernetes Helm chart definition diff --git a/apps/Makefile.in b/apps/Makefile.in index 2f096de0..b6513757 100644 --- a/apps/Makefile.in +++ b/apps/Makefile.in @@ -1,7 +1,7 @@ # # ***** 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 # @@ -44,6 +44,7 @@ SHELL = /bin/sh SUBDIRS = backend SUBDIRS += cli SUBDIRS += netconf +# See configure.ac ifeq ($(with_restconf),yes) SUBDIRS += restconf endif @@ -80,5 +81,5 @@ distclean: clean for i in $(SUBDIRS); \ do (cd $$i; $(MAKE) $(MFLAGS) $@); done -tags: +TAGS: find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 4d57dfc8..b3700e33 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -1,7 +1,7 @@ # # ***** 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 # diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index fedad77a..c2c370fe 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -2,7 +2,7 @@ * ***** 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. @@ -177,14 +177,19 @@ netconf_db_find(cxobj *xn, static int from_client_get_config(clicon_handle h, cxobj *xe, + char *username, cbuf *cbret) { - int retval = -1; - char *db; - cxobj *xfilter; - char *selector = "/"; - cxobj *xret = NULL; - cbuf *cbx = NULL; /* Assist cbuf */ + int retval = -1; + char *db; + cxobj *xfilter; + char *xpath = "/"; + cxobj *xret = NULL; + cbuf *cbx = NULL; /* Assist cbuf */ + cxobj *xnacm = NULL; + cxobj **xvec = NULL; + size_t xlen; + int ret; if ((db = netconf_db_find(xe, "source")) == NULL){ clicon_err(OE_XML, 0, "db not found"); @@ -201,13 +206,23 @@ from_client_get_config(clicon_handle h, goto ok; } if ((xfilter = xml_find(xe, "filter")) != NULL) - if ((selector = xml_find_value(xfilter, "select"))==NULL) - selector="/"; - if (xmldb_get(h, db, selector, 1, &xret) < 0){ + if ((xpath = xml_find_value(xfilter, "select"))==NULL) + xpath="/"; + if (xmldb_get(h, db, xpath, 1, &xret) < 0){ if (netconf_operation_failed(cbret, "application", "read registry")< 0) goto done; goto ok; } + /* Pre-NACM access step */ + if ((ret = nacm_access_pre(h, username, &xnacm)) < 0) + goto done; + if (ret == 0){ /* Do NACM validation */ + if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + /* NACM datanode/module read validation */ + if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0) + goto done; + } cprintf(cbret, ""); if (xret==NULL) cprintf(cbret, ""); @@ -221,6 +236,10 @@ from_client_get_config(clicon_handle h, ok: retval = 0; done: + if (xnacm) + xml_free(xnacm); + if (xvec) + free(xvec); if (cbx) cbuf_free(cbx); if (xret) @@ -292,7 +311,7 @@ client_get_streams(clicon_handle h, * @param[in,out] xret Existing XML tree, merge x into this * @retval -1 Error (fatal) * @retval 0 OK - * @retval 1 Statedata callback failed + * @retval 1 Statedata callback failed (clicon_err called) */ static int client_statedata(clicon_handle h, @@ -309,15 +328,15 @@ client_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") && - (retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") && - (retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) - goto done; - if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") && - (retval = yang_modules_state_get(h, yspec, xret)) != 0) - goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) + if ((retval = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")) + if ((retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0) + goto done; + if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")) + if ((retval = yang_modules_state_get(h, yspec, xret)) != 0) + goto done; if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0) goto done; /* Code complex to filter out anything that is outside of xpath */ @@ -339,7 +358,7 @@ client_statedata(clicon_handle h, /* reset flag */ if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; - retval = 0; + retval = 0; /* OK */ done: if (xvec) free(xvec); @@ -356,6 +375,7 @@ client_statedata(clicon_handle h, static int from_client_get(clicon_handle h, cxobj *xe, + char *username, cbuf *cbret) { int retval = -1; @@ -363,8 +383,10 @@ from_client_get(clicon_handle h, char *xpath = "/"; cxobj *xret = NULL; int ret; - cbuf *cbx = NULL; /* Assist cbuf */ - + cxobj **xvec = NULL; + size_t xlen; + cxobj *xnacm = NULL; + if ((xfilter = xml_find(xe, "filter")) != NULL) if ((xpath = xml_find_value(xfilter, "select"))==NULL) xpath="/"; @@ -379,33 +401,38 @@ from_client_get(clicon_handle h, clicon_err_reset(); if ((ret = client_statedata(h, xpath, &xret)) < 0) goto done; - if (ret == 0){ /* OK */ - cprintf(cbret, ""); - if (xret==NULL) - cprintf(cbret, ""); - else{ - if (xml_name_set(xret, "data") < 0) - goto done; - if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) - goto done; - } - cprintf(cbret, ""); - } - else { /* 1 Error from callback */ - if ((cbx = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); + if (ret == 1){ /* Error from callback (error in xret) */ + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) 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)); + goto ok; } + /* Pre-NACM access step */ + if ((ret = nacm_access_pre(h, username, &xnacm)) < 0) + goto done; + if (ret == 0){ /* Do NACM validation */ + if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) + goto done; + /* NACM datanode/module read validation */ + if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0) + goto done; + } + cprintf(cbret, ""); /* OK */ + if (xret==NULL) + cprintf(cbret, ""); + else{ + if (xml_name_set(xret, "data") < 0) + goto done; + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) + goto done; + } + cprintf(cbret, ""); ok: retval = 0; done: - if (cbx) - cbuf_free(cbx); + if (xnacm) + xml_free(xnacm); + if (xvec) + free(xvec); if (xret) xml_free(xret); return retval; @@ -422,6 +449,7 @@ static int from_client_edit_config(clicon_handle h, cxobj *xn, int mypid, + char *username, cbuf *cbret) { int retval = -1; @@ -433,14 +461,16 @@ from_client_edit_config(clicon_handle h, int non_config = 0; yang_spec *yspec; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec9"); goto done; } if ((target = netconf_db_find(xn, "target")) == NULL){ - clicon_err(OE_XML, 0, "db not found"); - goto done; + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; + goto ok; } if ((cbx = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -467,12 +497,18 @@ from_client_edit_config(clicon_handle h, goto ok; } } - if ((xc = xpath_first(xn, "config")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) + if ((xc = xpath_first(xn, "config")) == NULL){ + if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) goto done; goto ok; } else{ + /* yang spec may be set to anyxmly by ingress yang check,...*/ + if (xml_spec(xc) != NULL) + xml_spec_set(xc, NULL); + /* Populate XML with Yang spec (why not do this in parser?) + * Maybe validate xml here as in text_modify_top? + */ if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0) @@ -485,24 +521,27 @@ from_client_edit_config(clicon_handle h, /* Cant do this earlier since we dont have a yang spec to * 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; - if (xmldb_put(h, target, operation, xc, cbret) < 0){ + if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){ clicon_debug(1, "%s ERROR PUT", __FUNCTION__); if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) goto done; goto ok; } + if (ret == 0) + goto ok; } + assert(cbuf_len(cbret) == 0); + cprintf(cbret, ""); ok: - if (!cbuf_len(cbret)) - cprintf(cbret, ""); retval = 0; done: if (cbx) cbuf_free(cbx); clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; + } /* from_client_edit_config */ /*! Internal message: Lock database @@ -524,7 +563,7 @@ from_client_lock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -583,7 +622,7 @@ from_client_unlock(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -645,7 +684,7 @@ from_client_kill_session(clicon_handle h, if ((x = xml_find(xe, "session-id")) == NULL || (str = xml_find_value(x, "body")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) goto done; goto ok; } @@ -689,6 +728,10 @@ from_client_kill_session(clicon_handle h, * @param[out] cbret Return xml value cligen buffer * @retval 0 OK * @retval -1 Error. Send error message back to client. + * NACM: If source running and target startup --> only exec permission + * else: + * - omit data nodes to which the client does not have read access + * - access denied if user lacks create/delete/update */ static int from_client_copy_config(clicon_handle h, @@ -703,7 +746,7 @@ from_client_copy_config(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((source = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto ok; } @@ -718,7 +761,7 @@ from_client_copy_config(clicon_handle h, goto ok; } if ((target = netconf_db_find(xe, "target")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -771,7 +814,7 @@ from_client_delete_config(clicon_handle h, if ((target = netconf_db_find(xe, "target")) == NULL|| strcmp(target, "running")==0){ - if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) goto done; goto ok; } @@ -850,7 +893,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//stopTime")) != NULL){ if ((stoptime = xml_find_value(x, "body")) != NULL && str2time(stoptime, &stop) < 0){ - if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "stopTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -858,7 +901,7 @@ from_client_create_subscription(clicon_handle h, if ((x = xpath_first(xe, "//startTime")) != NULL){ if ((starttime = xml_find_value(x, "body")) != NULL && str2time(starttime, &start) < 0){ - if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) + if (netconf_bad_element(cbret, "application", "startTime", "Expected timestamp") < 0) goto done; goto ok; } @@ -919,7 +962,7 @@ from_client_debug(clicon_handle h, char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) goto done; goto ok; } @@ -935,272 +978,6 @@ from_client_debug(clicon_handle h, return retval; } -/*! Match nacm access operations according to RFC8341 3.4.4. - * Incoming RPC Message Validation Step 7 (c) - * The rule's "access-operations" leaf has the "exec" bit set or - * has the special value "*". - * @retval 0 No match - * @retval 1 Match - * XXX access_operations is bit-fields - */ -static int -nacm_match_access(char *access_operations, - char *mode) -{ - if (access_operations==NULL) - return 0; - if (strcmp(access_operations,"*")==0) - return 1; - if (strstr(access_operations, mode)!=NULL) - return 1; - return 0; -} - -/*! Match nacm single rule. Either match with access or deny. Or not match. - * @param[in] h Clicon handle - * @param[in] name rpc name - * @param[in] xrule NACM rule XML tree - * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. - * @retval -1 Error - * @retval 0 Matching rule AND Not access and cbret set - * @retval 1 Matchung rule AND Access - * @retval 2 No matching rule Goto step 10 - * From RFC8341 3.4.4. Incoming RPC Message Validation - +---------+-----------------+---------------------+-----------------+ - | Method | Resource class | NETCONF operation | Access | - | | | | operation | - +---------+-----------------+---------------------+-----------------+ - | OPTIONS | all | none | none | - | HEAD | all | , | read | - | GET | all | , | read | - | POST | datastore, data | | create | - | POST | operation | specified operation | execute | - | PUT | data | | create, update | - | PUT | datastore | | update | - | PATCH | data, datastore | | update | - | DELETE | data | | delete | - - 7.(cont) A rule matches if all of the following criteria are met: - * The rule's "module-name" leaf is "*" or equals the name of - the YANG module where the protocol operation is defined. - - * Either (1) the rule does not have a "rule-type" defined or - (2) the "rule-type" is "protocol-operation" and the - "rpc-name" is "*" or equals the name of the requested - protocol operation. - - * The rule's "access-operations" leaf has the "exec" bit set or - has the special value "*". - */ -static int -nacm_match_rule(clicon_handle h, - char *name, - cxobj *xrule, - cbuf *cbret) -{ - int retval = -1; - // cxobj *x; - char *module_name; - char *rpc_name; - char *access_operations; - char *action; - - module_name = xml_find_body(xrule, "module-name"); - rpc_name = xml_find_body(xrule, "rpc-name"); - /* XXX access_operations can be a set of bits */ - access_operations = xml_find_body(xrule, "access-operations"); - action = xml_find_body(xrule, "action"); - clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__, - module_name, rpc_name, access_operations, action); - if (module_name && strcmp(module_name,"*")==0){ - if (nacm_match_access(access_operations, "exec")){ - if (rpc_name==NULL || - strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){ - /* Here is a matching rule */ - if (action && strcmp(action, "permit")==0){ - retval = 1; - goto done; - } - else{ - if (netconf_access_denied(cbret, "protocol", "access denied") < 0) - goto done; - retval = 0; - goto done; - } - } - } - } - retval = 2; /* no matching rule */ - done: - return retval; - -} - -/*! Make nacm access control - * @param[in] h Clicon handle - * @param[in] mode NACMmode, internal or external - * @param[in] name rpc name - * @param[in] username - * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. - * @retval -1 Error - * @retval 0 Not access and cbret set - * @retval 1 Access - * From RFC8341 3.4.4. Incoming RPC Message Validation - */ -static int -nacm_access(clicon_handle h, - char *mode, - char *name, - char *username, - cbuf *cbret) -{ - int retval = -1; - cxobj *xtop = NULL; - cxobj *xacm; - cxobj *x; - cxobj *xrlist; - cxobj *xrule; - char *enabled = NULL; - cxobj **gvec = NULL; /* groups */ - size_t glen; - cxobj **rlistvec = NULL; /* rule-list */ - size_t rlistlen; - cxobj **rvec = NULL; /* rules */ - size_t rlen; - int i, j; - char *exec_default = NULL; - int ret; - - clicon_debug(1, "%s", __FUNCTION__); - /* 0. If nacm-mode is external, get NACM defintion from separet tree, - otherwise get it from internal configuration */ - if (strcmp(mode, "external")==0){ - if ((xtop = backend_nacm_list_get(h)) == NULL){ - clicon_err(OE_XML, 0, "No nacm external tree"); - goto done; - } - } - else if (strcmp(mode, "internal")==0){ - if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) - goto done; - } - else{ - clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode); - goto done; - } - - /* 1. If the "enable-nacm" leaf is set to "false", then the protocol - operation is permitted. (or config does not exist) */ - - if ((xacm = xpath_first(xtop, "nacm")) == NULL) - goto permit; - exec_default = xml_find_body(xacm, "exec-default"); - if ((x = xpath_first(xacm, "enable-nacm")) == NULL) - goto permit; - enabled = xml_body(x); - if (strcmp(enabled, "true") != 0) - goto permit; - - /* 2. If the requesting session is identified as a recovery session, - then the protocol operation is permitted. NYI */ - - /* 3. If the requested operation is the NETCONF - protocol operation, then the protocol operation is permitted. - */ - if (strcmp(name, "close-session") == 0) - goto permit; - /* 4. Check all the "group" entries to see if any of them contain a - "user-name" entry that equals the username for the session - making the request. (If the "enable-external-groups" leaf is - "true", add to these groups the set of groups provided by the - transport layer.) */ - if (username == NULL) - goto step10; - /* User's group */ - if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0) - goto done; - /* 5. If no groups are found, continue with step 10. */ - if (glen == 0) - goto step10; - /* 6. Process all rule-list entries, in the order they appear in the - configuration. If a rule-list's "group" leaf-list does not - match any of the user's groups, proceed to the next rule-list - entry. */ - if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0) - goto done; - for (i=0; i or , then the protocol operation - is denied. */ - if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){ - if (netconf_access_denied(cbret, "protocol", "default deny") < 0) - goto done; - goto deny; - } - /* 12. If the "exec-default" leaf is set to "permit", then permit the - protocol operation; otherwise, deny the request. */ - if (exec_default ==NULL || strcmp(exec_default, "permit")==0) - goto permit; - if (netconf_access_denied(cbret, "protocol", "default deny") < 0) - goto done; - goto deny; - permit: - retval = 1; - done: - clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); - if (strcmp(mode, "internal")==0 && xtop) - xml_free(xtop); - if (gvec) - free(gvec); - if (rlistvec) - free(rlistvec); - if (rvec) - free(rvec); - return retval; - deny: /* Here, cbret must contain a netconf error msg */ - assert(cbuf_len(cbret)); - retval = 0; - goto done; -} - /*! An internal clicon message has arrived from a client. Receive and dispatch. * @param[in] h Clicon handle * @param[in] s Socket where message arrived. read from this. @@ -1218,16 +995,21 @@ from_client_msg(clicon_handle h, cxobj *xt = NULL; cxobj *x; cxobj *xe; - char *name = NULL; + char *rpc = NULL; + char *module = NULL; char *db; cbuf *cbret = NULL; /* return message */ int pid; int ret; char *username; - char *nacm_mode; - + yang_spec *yspec; + yang_stmt *ye; + yang_stmt *ymod; + cxobj *xnacm = NULL; + clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; + yspec = clicon_dbspec_yang(h); /* Return netconf message. Should be filled in by the dispatch(sub) functions * as wither rpc-error or by positive response. */ @@ -1235,88 +1017,115 @@ from_client_msg(clicon_handle h, clicon_err(OE_XML, errno, "cbuf_new"); 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) goto done; goto reply; } + if ((x = xpath_first(xt, "/rpc")) == NULL){ if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } + /* Populate incoming XML tree with yang - + * 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) + goto done; + if ((ret = xml_yang_validate_rpc(x, cbret)) < 0) + goto done; + if (ret == 0) + goto reply; xe = NULL; username = xml_find_value(x, "username"); + /* May be used by callbacks, etc */ + clicon_username_set(h, username); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { - name = xml_name(xe); - clicon_debug(1, "%s name:%s", __FUNCTION__, name); - /* Make NACM access control if enabled as "internal"*/ - nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); - if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){ - if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0) + rpc = xml_name(xe); + if ((ye = xml_spec(xe)) == NULL){ + if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0) goto done; - if (!ret) + goto reply; + } + if ((ymod = ys_module(ye)) == NULL){ + clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); + goto done; + } + module = ymod->ys_argument; + clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); + /* Pre-NACM access step */ + xnacm = NULL; + if ((ret = nacm_access_pre(h, username, &xnacm)) < 0) + goto done; + if (ret == 0){ /* Do NACM validation */ + /* NACM rpc operation exec validation */ + if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0) + goto done; + if (xnacm) + xml_free(xnacm); + if (ret == 0) /* Not permitted and cbret set */ goto reply; } - if (strcmp(name, "get-config") == 0){ - if (from_client_get_config(h, xe, cbret) <0) + if (strcmp(rpc, "get-config") == 0){ + if (from_client_get_config(h, xe, username, cbret) <0) goto done; } - else if (strcmp(name, "edit-config") == 0){ - if (from_client_edit_config(h, xe, pid, cbret) <0) + else if (strcmp(rpc, "edit-config") == 0){ + if (from_client_edit_config(h, xe, pid, username, cbret) <0) goto done; } - else if (strcmp(name, "copy-config") == 0){ + else if (strcmp(rpc, "copy-config") == 0){ if (from_client_copy_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "delete-config") == 0){ + else if (strcmp(rpc, "delete-config") == 0){ if (from_client_delete_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "lock") == 0){ + else if (strcmp(rpc, "lock") == 0){ if (from_client_lock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "unlock") == 0){ + else if (strcmp(rpc, "unlock") == 0){ if (from_client_unlock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "get") == 0){ - if (from_client_get(h, xe, cbret) < 0) + else if (strcmp(rpc, "get") == 0){ + if (from_client_get(h, xe, username, cbret) < 0) goto done; } - else if (strcmp(name, "close-session") == 0){ + else if (strcmp(rpc, "close-session") == 0){ xmldb_unlock_all(h, pid); stream_ss_delete_all(h, ce_event_cb, (void*)ce); cprintf(cbret, ""); } - else if (strcmp(name, "kill-session") == 0){ + else if (strcmp(rpc, "kill-session") == 0){ if (from_client_kill_session(h, xe, cbret) < 0) goto done; } - else if (strcmp(name, "validate") == 0){ + else if (strcmp(rpc, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ - if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; goto reply; } if (from_client_validate(h, db, cbret) < 0) goto done; } - else if (strcmp(name, "commit") == 0){ + else if (strcmp(rpc, "commit") == 0){ if (from_client_commit(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "discard-changes") == 0){ + else if (strcmp(rpc, "discard-changes") == 0){ if (from_client_discard_changes(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "create-subscription") == 0){ + else if (strcmp(rpc, "create-subscription") == 0){ if (from_client_create_subscription(h, xe, ce, cbret) < 0) goto done; } - else if (strcmp(name, "debug") == 0){ + else if (strcmp(rpc, "debug") == 0){ if (from_client_debug(h, xe, cbret) < 0) goto done; } @@ -1370,7 +1179,7 @@ from_client_msg(clicon_handle h, /* Sanity: log if clicon_err() is not called ! */ if (retval < 0 && clicon_errno < 0) clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)", - __FUNCTION__, name?name:""); + __FUNCTION__, rpc?rpc:""); // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 81620d4a..c9a1da11 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -2,7 +2,7 @@ * ***** 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. diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 3aa2f82d..453e3e6c 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -2,7 +2,7 @@ * ***** 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. @@ -81,78 +81,108 @@ * are if code comes via XML/NETCONF. * @param[in] yspec Yang spec * @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 generic_validate(yang_spec *yspec, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; cxobj *x1; cxobj *x2; yang_stmt *ys; int i; + int ret; /* All entries */ - if (xml_apply(td->td_target, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) + if ((ret = xml_yang_validate_all_top(td->td_target, cbret)) < 0) goto done; - + if (ret == 0) + goto fail; /* changed entries */ for (i=0; itd_clen; i++){ x1 = td->td_scvec[i]; /* source 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; + if (ret == 0) + goto fail; } /* deleted entries */ for (i=0; itd_dlen; i++){ x1 = td->td_dvec[i]; ys = xml_spec(x1); - if (ys && yang_mandatory(ys)){ - clicon_err(OE_CFG, 0,"Removed mandatory variable: %s", - xml_name(x1)); - goto done; + if (ys && yang_mandatory(ys) && yang_config(ys)==0){ + if (netconf_missing_element(cbret, "protocol", xml_name(x1), "Missing mandatory variable") < 0) + goto done; + goto fail; } } /* added entries */ for (i=0; itd_alen; i++){ x2 = td->td_avec[i]; - if (xml_apply0(x2, CX_ELMNT, - (xml_applyfn_t*)xml_yang_validate_add, NULL) < 0) + if ((ret = xml_yang_validate_add(x2, cbret)) < 0) goto done; + if (ret == 0) + goto fail; } - retval = 0; + // ok: + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Common code of candidate_validate and candidate_commit * @param[in] h Clicon handle * @param[in] candidate The candidate database. The wanted backend state - * @retval 0 OK - * @retval -1 Fatal error or validation fail - * @note Need to differentiate between error and validation fail + * @retval -1 Error - or validation failed (but cbret not set) + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK + * @note Need to differentiate between error and validation fail + * (only done for generic_validate) */ static int validate_common(clicon_handle h, char *candidate, - transaction_data_t *td) + transaction_data_t *td, + cbuf *cbret) { int retval = -1; yang_spec *yspec; int i; cxobj *xn; - + int ret; + if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); 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) goto done; + /* This is the state we are going to */ if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) 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 */ if (xml_diff(yspec, td->td_src, @@ -193,9 +223,12 @@ validate_common(clicon_handle h, if (plugin_transaction_begin(h, td) < 0) goto done; - /* 5. Make generic validation on all new or changed data. */ - if (generic_validate(yspec, td) < 0) + /* 5. Make generic validation on all new or changed data. + Note this is only call that uses 3-values */ + if ((ret = generic_validate(yspec, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 6. Call plugin transaction validate callbacks */ if (plugin_transaction_validate(h, td) < 0) @@ -204,9 +237,12 @@ validate_common(clicon_handle h, /* 7. Call plugin transaction complete callbacks */ if (plugin_transaction_complete(h, td) < 0) goto done; - retval = 0; + retval = 1; done: return retval; + fail: + retval = 0; + goto done; } /*! Do a diff between candidate and running, then start a commit transaction @@ -216,57 +252,70 @@ validate_common(clicon_handle h, * do something more drastic? * @param[in] h Clicon handle * @param[in] candidate A candidate database, not necessarily "candidate" - * @retval 0 OK - * @retval -1 Fatal error or validation fail + * @retval -1 Error - or validation failed + * @retval 0 Validation failed (with cbret set) + * @retval 1 Validation OK * @note Need to differentiate between error and validation fail + * (only done for validate_common) */ int candidate_commit(clicon_handle h, - char *candidate) + char *candidate, + cbuf *cbret) { - int retval = -1; + int retval = -1; transaction_data_t *td = NULL; + int ret; /* 1. Start transaction */ if ((td = transaction_new()) == NULL) goto done; - /* Common steps (with validate) */ - if (validate_common(h, candidate, td) < 0) + /* Common steps (with validate). Load candidate and running and compute diffs + * Note this is only call that uses 3-values + */ + if ((ret = validate_common(h, candidate, td, cbret)) < 0) goto done; + if (ret == 0) + goto fail; /* 7. Call plugin transaction commit callbacks */ if (plugin_transaction_commit(h, td) < 0) goto done; /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, + clicon_username_get(h), cbret)) < 0) goto done; + if (ret == 0) + goto fail; + } /* 8. Success: Copy candidate to running */ - if (xmldb_copy(h, candidate, "running") < 0) goto done; /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); - /* 8. Copy running back to candidate in case end functions updated running */ if (xmldb_copy(h, "running", candidate) < 0){ /* ignore errors or signal major setback ? */ clicon_log(LOG_NOTICE, "Error in rollback, trying to continue"); goto done; } - retval = 0; + retval = 1; done: - /* In case of failure, call plugin transaction termination callbacks */ - if (retval < 0 && td) + /* In case of failure (or error), call plugin transaction termination callbacks */ + if (retval < 1 && td) plugin_transaction_abort(h, td); if (td) transaction_free(td); return retval; + fail: + retval = 0; + goto done; } /*! Commit changes from candidate to running @@ -274,6 +323,10 @@ candidate_commit(clicon_handle h, * @param[out] cbret Return xml value cligen buffer * @retval 0 OK. This may indicate both ok and err msg back to client * @retval -1 (Local) Error + * NACM: The server MUST determine the exact nodes in the running + * configuration datastore that are actually different and only check + * "create", "update", and "delete" access permissions for this set of + * nodes, which could be empty. */ int from_client_commit(clicon_handle h, @@ -283,6 +336,7 @@ from_client_commit(clicon_handle h, int retval = -1; int piddb; cbuf *cbx = NULL; /* Assist cbuf */ + int ret; /* Check if target locked by other client */ piddb = xmldb_islocked(h, "running"); @@ -296,9 +350,10 @@ from_client_commit(clicon_handle h, goto done; 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"); - 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 ok; } @@ -317,6 +372,7 @@ from_client_commit(clicon_handle h, * @param[out] cbret Return xml value cligen buffer * @retval 0 OK. This may indicate both ok and err msg back to client * @retval -1 (Local) Error + * NACM: No datastore permissions are needed. */ int from_client_discard_changes(clicon_handle h, @@ -367,6 +423,7 @@ from_client_validate(clicon_handle h, { int retval = -1; transaction_data_t *td = NULL; + int ret; if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) @@ -379,17 +436,21 @@ from_client_validate(clicon_handle h, if ((td = transaction_new()) == NULL) goto done; /* 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); - /* XXX: candidate_validate should have proper error handling */ - if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) - goto done; + if (ret < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + } goto ok; } /* Optionally write (potentially modified) tree back to candidate */ - if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) - goto done; + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){ + if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, + clicon_username_get(h), cbret)) < 0) + goto done; + goto ok; + } cprintf(cbret, ""); ok: retval = 0; diff --git a/apps/backend/backend_commit.h b/apps/backend/backend_commit.h index 6be05858..3af2461a 100644 --- a/apps/backend/backend_commit.h +++ b/apps/backend/backend_commit.h @@ -2,7 +2,7 @@ * ***** 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. @@ -43,6 +43,6 @@ 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_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_ */ diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 676cc50c..f2acea6f 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -2,7 +2,7 @@ * ***** 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. @@ -52,8 +52,4 @@ struct client_entry *backend_client_list(clicon_handle h); int backend_client_delete(clicon_handle h, struct client_entry *ce); -int backend_nacm_list_set(clicon_handle h, cxobj *xnacm); - -cxobj * backend_nacm_list_get(clicon_handle h); - #endif /* _BACKEND_HANDLE_H_ */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 3856eb0e..93e25bed 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -2,7 +2,7 @@ * ***** 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. @@ -73,7 +73,7 @@ #include "backend_handle.h" /* 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:p:b:Fza:u:P:1s:c:g:y:x:o:" #define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log" @@ -91,6 +91,8 @@ backend_terminate(clicon_handle h) yspec_free(yspec); if ((yspec = clicon_config_yang(h)) != NULL) yspec_free(yspec); + if ((x = clicon_nacm_ext(h)) != NULL) + xml_free(x); if ((x = clicon_conf_xml(h)) != NULL) xml_free(x); stream_publish_exit(); @@ -140,6 +142,7 @@ usage(clicon_handle h, "\t-f \tCLICON config file\n" "\t-l (s|e|o|f) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n" "\t-d \tSpecify backend plugin directory (default: %s)\n" + "\t-p \tYang directory path (see CLICON_YANG_DIR)\n" "\t-b \tSpecify XMLDB database directory\n" "\t-F\t\tRun in foreground, do not run as daemon\n" "\t-z\t\tKill other config daemon and exit\n" @@ -152,7 +155,8 @@ usage(clicon_handle h, "\t-g \tClient membership required to this group (default: %s)\n" "\t-y \tLoad yang spec file (override yang main module)\n" - "\t-x \tXMLDB plugin\n", + "\t-x \tXMLDB plugin\n" + "\t-o \"