diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a7653d..65020e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,126 @@ # Clixon Changelog +## 3.6.0 (Upcoming) + +### Major changes: +* Experimental NACM RFC8341 Network Configuration Access Control Model, see [NACM](README_NACM.md). + * New CLICON_NACM_MODE config option, default is disabled. + * New CLICON_NACM_FILE config option, if CLICON_NACM_MODE is "external" + * Added username attribute to all internal RPC:s from frontend to backend + * Added NACM backend module in example +* Restructure and more generic plugin API for cli, backend, restconf, and netconf. See example further down and the [example](example/README.md) + * Changed `plugin_init()` to `clixon_plugin_init()` returning an api struct with function pointers. There are no other hardcoded plugin functions. + * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. + * Plugin RPC callback interface have been unified between backend, netconf and restconf. + * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: + `backend_rpc_cb_register()` to `rpc_callback_register()` + * Backend RPC callback signature has been changed from: + `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + to: + `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. + * Moved specific plugin functions from apps/ to generic functions in lib/ + * New config option CLICON_BACKEND_REGEXP to match backend plugins (if you do not want to load all). + * Added authentication plugin callback (ca_auth) + * Added clicon_username_get() / clicon_username_set() + * Removed some obscure plugin code that seem not to be used (please report if needed!) + * CLI parse hook + * CLICON_FIND_PLUGIN + * clicon_valcb() + * CLIXON_BACKEND_SYSDIR + * CLIXON_CLI_SYSDIR + * CLICON_MASTER_PLUGIN config variable + * Example of migrating a backend plugin module: + * Add all callbacks in a clixon_plugin_api struct + * Rename plugin_init() -> clixon_plugin_init() and return api as function value + * Rename backend_rpc_cb_register() -> rpc_callback_register() for any RPC/restconf operation POST calls +``` +/* This is old style with hardcoded function names (eg plugin_start) */ +int plugin_start(clicon_handle h, int argc, char **argv) +{ + return 0; +} +int +plugin_init(clicon_handle h) +{ + return 0; +} + +/* This is new style with all function names in api struct */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + .ca_auth=plugin_credentials /* restconf specific: auth */ +}; + +clixon_plugin_api *clixon_plugin_init(clicon_handle h) +{ + return &api; /* Return NULL on error */ +} +``` + +* Builds and installs a new restconf library: `libclixon_restconf.so` and clixon_restconf.h + * The restconf library can be included by a restconf plugin. + * Example code in example/Makefile.in and example/restconf_lib.c +* Restconf error handling for get, put and post. (thanks Stephen Jones, Netgate) + * Available both as xml and json (set accept header). +* Proper RFC 6241 Netconf error handling + * New functions added in clixon_netconf_lib.[ch] + * Datastore code modified for RFC 6241 + +### Minor changes: + +* INSTALLFLAGS added with default value -s(strip). + * For debug do: CFLAGS=-g INSTALLFLAGS= ./configure +* plugin_start() callbacks added for restconf +* Authentication + * Example extended with http basic authentication for restconf + * Documentation in FAQ.md +* Updated ietf-netconf-acm to ietf-netconf-acm@2018-02-14.yang from RFC 8341 +* The Clixon example has changed name from "routing" to "example" affecting all config files, plugins, tests, etc. + * Secondary backend plugin added +* Removed username to rpc calls (added below) +* README.md extended with new yang, netconf, restconf, datastore, and auth sections. +* The key-value datastore is no longer supported. Use the default text datastore. +* Added username to rpc calls to prepare for authorization for backend: + * clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) + * clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) +* Experimental: Added CLICON_TRANSACTION_MOD configuration option. If set, + modifications in validation and commit callbacks are written back + into the datastore. Requested by Stephen Jones, Netgate. +* Invalid key to api_path2xml gives warning instead of error and quit. +* Added restconf/operations get, see RFC8040 Sec 3.3.2: +* yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE + +* xml2json: include prefix in translation, so is translated to {"a:b" ..} +* Use `` instead of `` when save/load configuration to file. This +enables saved files to be used as datastore without any editing. Thanks Matt, Netgate. + +* Added Yang "extension" statement. This includes parsing unknown + statements and identifying them as extensions or not. However, + semantics for specific extensions must still be added. + +* Renamed ytype_id and ytype_prefix to yarg_id and yarg_prefix, respectively + +* Added cli_show_version() + +### Corrected Bugs +* Showing syntax using CLI commands was broekn and is fixed. +* Fixed issue https://github.com/clicon/clixon/issues/18 RPC response issues reported by Stephen Jones at Netgate +* Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs reported by David Cornejo at Netgate. + * This was a large rewrite of XML parsing and output due to CharData not correctly encoded according to https://www.w3.org/TR/2008/REC-xml-20081126. +* Fixed three-key list entry problem (reported by jdl@netgate) +* Translate xml->json \n correctly +* Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config + ## 3.5.0 (12 February 2018) ### Major changes: -* Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right. +* Major Restconf feature update to comply to RFC 8040. Thanks Stephen Jones of Netgate for getting right. * GET: Always return object referenced (and nothing else). ie, GET /restconf/data/X returns X. * GET Added support for the following resources: Well-known, top-level resource, and yang library version, * GET Single element JSON lists use {list:[element]}, not {list:element}. @@ -42,7 +159,7 @@ * New CLICON_XML_SORT configuration option. Default is true. Disable by setting to false. * Added yang ordered-by user. The default (ordered-by system) will now sort lists and leaf-lists alphabetically to increase search performance. Note that this may change outputs. * If you need legacy order, either set CLICON_XML_SORT to false, or set that list to "ordered-by user". - * This replaces XML hash experimental code, ie xml_child_hash variables and all xml_hash_ functions have been removed. + * This replaces XML hash experimental code, ie xml_child_hash variables and all xmlv_hash_ functions have been removed. * Implementation detail: Cached keys are stored in in yang Y_LIST nodes as cligen vector, see ys_populate_list() * Datastore cache introduced: cache XML tree in memory for faster get access. @@ -102,11 +219,11 @@ SUNET for support, requests, debugging, bugfixes and proposed solutions. * In backward compatible mode both .xml and .conf works * For migration from old to new, a utility is clixon_cli -x to print new format. Run the command and save in configuration file with .xml suffix instead. ``` - > clixon_cli -f /usr/local/etc/routing.conf -1x + > clixon_cli -f /usr/local/etc/example.conf -1x - /usr/local/etc/routing.xml - /usr/local/share/routing/yang - /usr/local/lib/routing/backend + /usr/local/etc/example.xml + /usr/local/share/example/yang + /usr/local/lib/example/backend ... ``` diff --git a/Makefile.in b/Makefile.in index 3041750f..04c72ab5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,7 @@ CC = @CC@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ -#INSTALL = @INSTALL@ +INSTALL = @INSTALL@ INCLUDES = -I. -I@srcdir@ @INCLUDES@ SHELL = /bin/sh @@ -69,8 +69,8 @@ clixon.mk: clixon.mk.cpp $(CPP) -P -traditional-cpp -x assembler-with-cpp -Dprefix=$(prefix) -Dlocalstatedir=$(localstatedir) -Dsysconfdir=$(sysconfdir) -Ddatadir=$(datadir) -Dlibdir=$(libdir) $< > $@ install: clixon.mk - install -d -m 755 $(DESTDIR)$(datadir)/clixon - install -m 755 clixon.mk $(DESTDIR)$(datadir)/clixon + install -d -m 0755 $(DESTDIR)$(datadir)/clixon + install -m 0644 clixon.mk $(DESTDIR)$(datadir)/clixon for i in $(SUBDIRS) doc; \ do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done; \ echo "Install for compilation by: make install-include" diff --git a/README.md b/README.md index 141e78b6..44198776 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,39 @@ Clixon is an automatic configuration manager where you generate interactive CLI, NETCONF, RESTCONF and embedded databases with transaction support from a YANG specification. -Table of contents -================= - * [Documentation](#documentation) - * [Installation](#installation) - * [Dependencies](#dependencies) - * [Licenses](#licenses) * [Background](#background) - * [Clixon SDK](#SDK) + * [Frequently asked questions](doc/FAQ.md) + * [Installation](#installation) + * [Licenses](#licenses) + * [Support](#support) + * [Dependencies](#dependencies) + * [Extending](#extending) + * [Yang](#yang) + * [Netconf](#netconf) + * [Restconf](#restconf) + * [Datastore](datastore/README.md) + * [Authentication and Authorization](#auth) + * [Example](example/) + * [Changelog](CHANGELOG.md) + * [Runtime](#runtime) + * [Clixon project page](http://www.clicon.org) + * [Tests](test/) + * [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`) + +Background +========== -Documentation -============= -- [Frequently asked questions](doc/FAQ.md) -- [CHANGELOG](CHANGELOG.md) recent changes. -- [XML datastore](datastore/README.md) -- [Netconf support](apps/netconf/README.md) -- [Restconf support](apps/restconf/README.md) -- [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`) -- [Routing example](example/README.md) -- [Clicon and Clixon project page](http://www.clicon.org) -- [Tests](test/README.md) +Clixon was implemented to provide an open-source generic configuration +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, whilke clixon is a system with config-db, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. + +Users of clixon currently include: + * [Netgate](https://www.netgate.com) + * [CloudMon360](http://cloudmon360.com) + * [Grideye](http://hagsand.se/grideye) + * [Netclean](https://www.netclean.com/solutions/whitebox) # only CLIgen + * [Prosilient's PTAnalyzer](https://prosilient.com) # only CLIgen + +See also [Clicon project page](http://clicon.org). Installation ============ @@ -36,13 +49,19 @@ A typical installation is as follows: ``` One [example application](example/README.md) is provided, a IETF IP YANG datamodel with -generated CLI and configuration interface. +generated CLI, Netconf and restconf interface. + +Licenses +======== +Clixon is open-source and dual licensed. Either Apache License, Version 2.0 or GNU +General Public License Version 2; you choose. + +See [LICENSE.md](LICENSE.md) for the license. Dependencies ============ Clixon depends on the following software packages, which need to exist on the target machine. -- [CLIgen](http://www.cligen.se) is required for building Clixon. If you need -to build and install CLIgen: +- [CLIgen](http://www.cligen.se) If you need to build and install CLIgen: ``` git clone https://github.com/olofhagsand/cligen.git cd cligen; configure; make; make install @@ -50,53 +69,115 @@ to build and install CLIgen: - Yacc/bison - Lex/Flex - Fcgi (if restconf is enabled) -- [Qdbm](http://fallabs.com/qdbm/) key-value store (if keyvalue datastore is enabled) There is no yum/apt/ostree package for Clixon (please help?) -Licenses +Support +======= +Clixon interaction is best done posting issues, pull requests, or joining the +[slack channel](https://clixondev.slack.com). +[Slack invite](https://join.slack.com/t/clixondev/shared_invite/enQtMzI3OTM4MzA3Nzk3LTA3NWM4OWYwYWMxZDhiYTNhNjRkNjQ1NWI1Zjk5M2JjMDk4MTUzMTljYTZiYmNhODkwMDI2ZTkyNWU3ZWMyN2U). + +Extending +========= +Clixon provides a core system and can be used as-is using available +Yang specifications. However, an application very quickly needs to +specialize functions. Clixon is extended by writing +plugins for cli and backend. Extensions for netconf and restconf +are also available. + +Plugins are written in C and easiest is to look at +[example](example/README.md) or consulting the [FAQ](doc/FAQ.md). + +Yang +==== + +YANG and XML is at 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. + +Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions: +- conformance: feature, if-feature, deviation +- identity, base, identityref +- list features: min/max-elements, unique + +The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) + +Clixon has its own XML library designed for performance. + +Netconf +======= +Clixon implements the following NETCONF proposals or standards: +- [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt) +- [Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt) +- [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt) + +Some updates are being made to RFC 6241 and RFC 6242. + +Clixon does not yet support the following netconf features: + +- :url capability +- copy-config source config +- edit-config testopts +- edit-config erropts +- edit-config config-text + +Restconf ======== -Clixon is dual licensed. Either Apache License, Version 2.0 or GNU -General Public License Version 2; you choose. +Clixon restconf is a daemon based on FASTCGI. Instructions are available to +run with NGINX. +The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040). +The following features are supported: +- OPTIONS, HEAD, GET, POST, PUT, DELETE +The following are not implemented +- PATCH +- query parameters (section 4.9) +- notifications (sec 6) +- schema resource -See [LICENSE.md](LICENSE.md) for the license. +See [more detailed instructions](apps/restconf/README.md). -Background -========== -We implemented Clixon since we needed a generic configuration tool in -several projects, including -[KTH](http://www.csc.kth.se/~olofh/10G_OSR). Most of these projects -were for embedded network and measuring-probe devices. We started with -something called Clicon which was based on a key-value specification -and data-store. But as time passed new standards evolved and we -started adapting it to XML, Yang and netconf. Finally we made Clixon, -where the legacy key specification has been replaced completely by -YANG and using XML as configuration data. This means that legacy -Clicon applications do not run on Clixon. +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. -SDK -=== +Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented. -clixon sdk +The datastore is primarily designed to be used by Clixon but can be used +separately. + +See [more detailed instructions](datastore/README.md). + +Auth +==== + +Authentication is managed outside Clixon using SSH, SSL, Oauth2, etc. + +For CLI, login is typically made via SSH. For netconf, SSH netconf +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) + +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 +so the clients can in principle fake a username. + +There is an ongoing effort to implement authorization for Clixon +according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at +least a subset of the functionality. See more information here: +[NACM](README_NACM.md). + + +Runtime +======= + +clixon sdk The figure shows the SDK runtime of Clixon. -YANG and XML is at the heart of Clixon. Yang modules are used as a -specification for handling XML configuration data. The spec is also -used to generate an interactive CLI client as well as provide -[Netconf](apps/netconf/README.md) and -[Restconf](apps/restconf/README.md) clients. - -The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions: -- conformance: feature, if-feature, deviation -- identity, base, identityref -- list features: min/max-elements, unique, ordered-by - -There are also new features in YANG 1.1 [YANG RFC -7950](https://www.rfc-editor.org/rfc/rfc7950.txt), most of which are -not implemented. - - - - - diff --git a/develop.md b/README_DEVELOP.md similarity index 75% rename from develop.md rename to README_DEVELOP.md index c23f5314..350d3636 100644 --- a/develop.md +++ b/README_DEVELOP.md @@ -1,11 +1,12 @@ -# README for developers Clixon developers +# README for Clixon developers -1. How to document the code -2. How to work in git (branching) -3. How the meta-configure stuff works -4. How to debug + * [Code documentation](#documentation) + * [How to work in git (branching)](#branching) + * [How the meta-configure stuff works](#meta-configure) + * [How to debug](#debug) -## How to document the code +## Documentation +How to document the code ``` /*! This is a small comment on one line @@ -26,7 +27,8 @@ */ ``` -## How to work in git (branching) +## Branching +How to work in git (branching) Basically follows: http://nvie.com/posts/a-successful-git-branching-model/ only somewhat simplified: @@ -49,11 +51,16 @@ configure.ac --. Makefile.in ---' `-> Makefile ---' ``` -## How to debug +## Debug +How to debug + +### Configure in debug mode +``` + CFLAGS="-g -Wall" INSTALLFLAGS="" ./configure +``` ### Make your own simplified yang and configuration file. ``` - cat < /tmp/my.yang module mymodule{ container x { @@ -69,11 +76,11 @@ EOF cat < /tmp/myconf.xml /tmp/myconf.xml - /usr/local/share/routing/yang + /usr/local/share/example/yang example - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - /usr/local/var/routing + /usr/local/var/example/example.sock + /usr/local/var/example/example.pidfile + /usr/local/var/example /usr/local/lib/xmldb/text.so EOF diff --git a/README_NACM.md b/README_NACM.md new file mode 100644 index 00000000..7ec76ee3 --- /dev/null +++ b/README_NACM.md @@ -0,0 +1,34 @@ +# NACM + +Clixon includes an experimental NACM implementation according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). + +The support is as follows: + +* 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. +* 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 functionality +================== + +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 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 diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 662b884b..4d57dfc8 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -36,6 +36,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -55,8 +56,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ # Use this clixon lib for linking CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) -# Location of system plugins -CLIXON_BACKEND_SYSDIR = $(libdir)/clixon/plugins/backend # For dependency. A little strange that we rely on it being built in the src dir # even though it may exist in $(libdir). But the new version may not have been installed yet. @@ -66,23 +65,27 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -l CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -# Not accessible from plugin -APPSRC = backend_main.c backend_socket.c backend_client.c \ - backend_commit.c backend_plugin.c - -APPOBJ = $(APPSRC:.c=.o) +# Name of application APPL = clixon_backend -#SHLIB = clixon_backend -MYNAME = clixon_backend -MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) -MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) -MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) +# Not accessible from plugin +APPSRC = backend_main.c +APPSRC += backend_socket.c +APPSRC += backend_client.c +APPSRC += backend_commit.c +APPSRC += backend_plugin.c +APPOBJ = $(APPSRC:.c=.o) # Accessible from plugin LIBSRC = clixon_backend_transaction.c clixon_backend_handle.c LIBOBJ = $(LIBSRC:.c=.o) +# Name of lib +MYNAME = clixon_backend +MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) +MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) +MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) + all: $(MYLIB) $(APPL) test clean: @@ -96,15 +99,15 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(sbindir) - install $(APPL) $(DESTDIR)$(sbindir) + install -d -m 0755 $(DESTDIR)$(sbindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(sbindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_config.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_config.so - install -d $(DESTDIR)$(libdir)/clixon/plugins/backend + install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/backend uninstall: rm -f $(DESTDIR)$(sbindir)/$(APPL) @@ -112,14 +115,14 @@ uninstall: rm -f $(DESTDIR)$(includedir)/clixon/* install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transaction.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon .SUFFIXES: .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_BACKEND_SYSDIR=\"$(CLIXON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $< + $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< # Just link test programs test.c : @@ -129,7 +132,7 @@ test: test.c $(LIBOBJ) $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(MYLIB): $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) @@ -147,7 +150,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 97b0ae79..fc619640 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -180,7 +180,7 @@ backend_client_rm(clicon_handle h, return backend_client_delete(h, ce); /* actually purge it */ } -/*! FInd target/source in netconf request. Assume sanity made so not finding is error */ +/*! Find target/source in netconf request. Assume sanity- not finding is error */ static char* netconf_db_find(cxobj *xn, char *name) @@ -214,31 +214,28 @@ from_client_get_config(clicon_handle h, cxobj *xfilter; char *selector = "/"; cxobj *xret = NULL; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "source")) == NULL){ clicon_err(OE_XML, 0, "db not found"); goto done; } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; 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){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", "read registry")< 0) + goto done; goto ok; } cprintf(cbret, ""); @@ -254,6 +251,8 @@ from_client_get_config(clicon_handle h, ok: retval = 0; done: + if (cbx) + cbuf_free(cbx); if (xret) xml_free(xret); return retval; @@ -276,24 +275,21 @@ from_client_get(clicon_handle h, char *selector = "/"; cxobj *xret = NULL; int ret; + cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) if ((selector = xml_find_value(xfilter, "select"))==NULL) selector="/"; /* Get config */ if (xmldb_get(h, "running", selector, 0, &xret) < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", "read registry")< 0) + goto done; goto ok; } /* Get state data from plugins as defined by plugin_statedata(), if any */ assert(xret); clicon_err_reset(); - if ((ret = backend_statedata_call(h, selector, xret)) < 0) + if ((ret = clixon_plugin_statedata(h, selector, &xret)) < 0) goto done; if (ret == 0){ /* OK */ cprintf(cbret, ""); @@ -308,23 +304,25 @@ from_client_get(clicon_handle h, cprintf(cbret, ""); } else { /* 1 Error from callback */ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error:%s" - "", clicon_err_reason); + 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: retval = 0; done: + if (cbx) + cbuf_free(cbx); if (xret) xml_free(xret); return retval; } - /*! Internal message: edit-config * * @param[in] h Clicon handle @@ -340,67 +338,60 @@ from_client_edit_config(clicon_handle h, { int retval = -1; char *target; - cbuf *cb = NULL; - cxobj *xret = NULL; cxobj *xc; cxobj *x; enum operation_type operation = OP_MERGE; int piddb; int non_config = 0; yang_spec *yspec; + cbuf *cbx = NULL; /* Assist cbuf */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); + 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 ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if ((x = xpath_first(xn, "default-operation")) != NULL){ if (xml_operation(xml_body(x), &operation) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - ""); + if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0) + goto done; goto ok; } } - if ((xc = xpath_first(xn, "config")) != NULL){ + if ((xc = xpath_first(xn, "config")) == NULL){ + if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) + goto done; + goto ok; + } + else{ 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) goto done; if (non_config){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "state data not allowed" - ""); + if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 0) + goto done; goto ok; } /* Cant do this earlier since we dont have a yang spec to @@ -408,35 +399,23 @@ from_client_edit_config(clicon_handle h, */ if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xmldb_put(h, target, operation, xc) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "%s" - "", clicon_err_reason); + if (xmldb_put(h, target, operation, xc, cbret) < 0){ + clicon_debug(1, "%s ERROR PUT", __FUNCTION__); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } } - else{ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "config" - ""); - goto ok; - } - cprintf(cbret, ""); ok: + if (!cbuf_len(cbret)) + cprintf(cbret, ""); retval = 0; done: - if (xret) - xml_free(xret); - if (cb) - cbuf_free(cb); + 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 * @@ -454,26 +433,23 @@ from_client_lock(clicon_handle h, int retval = -1; char *db; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + 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"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - /* * A lock MUST not be granted if either of the following conditions is true: * 1) A lock is already held by any NETCONF session or another entity. @@ -482,23 +458,21 @@ from_client_lock(clicon_handle h, */ piddb = xmldb_islocked(h, db); if (piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Lock failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } else{ - xmldb_lock(h, db, pid); + if (xmldb_lock(h, db, pid) < 0) + goto done; cprintf(cbret, ""); } ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -518,23 +492,21 @@ from_client_unlock(clicon_handle h, int retval = -1; char *db; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + 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"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } piddb = xmldb_islocked(h, db); @@ -546,14 +518,9 @@ from_client_unlock(clicon_handle h, * session that obtained the lock */ if (piddb==0 || piddb != pid){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Unlock failed, lock is already held" - "pid=%d piddb=%d" - "", - pid, piddb); + cprintf(cbx, "pid=%d piddb=%d", pid, piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock is already held") < 0) + goto done; goto ok; } else{ @@ -585,15 +552,11 @@ from_client_kill_session(clicon_handle h, struct client_entry *ce; char *db = "running"; /* XXX */ cxobj *x; - + if ((x = xml_find(xe, "session-id")) == NULL || (str = xml_find_value(x, "body")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "session-id" - ""); + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + goto done; goto ok; } pid = atoi(str); @@ -618,18 +581,14 @@ from_client_kill_session(clicon_handle h, xmldb_unlock(h, db); } else{ /* failed to kill client */ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "Faile to kill session" - ""); + if (netconf_operation_failed(cbret, "application", "Failed to kill session")< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: return retval; } @@ -647,74 +606,57 @@ from_client_copy_config(clicon_handle h, int mypid, cbuf *cbret) { - char *source; - char *target; - int retval = -1; - int piddb; - + char *source; + char *target; + int retval = -1; + int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ + if ((source = netconf_db_find(xe, "source")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "source" - ""); + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; goto ok; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(source) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", source); + cprintf(cbx, "No such database: %s", source); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - if ((target = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; goto ok; } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_copy(h, source, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -732,67 +674,51 @@ from_client_delete_config(clicon_handle h, int mypid, cbuf *cbret) { - int retval = -1; - char *target; - int piddb; + int retval = -1; + char *target; + int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((target = netconf_db_find(xe, "target")) == NULL|| strcmp(target, "running")==0){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + 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"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_delete(h, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "Internal error" - "%s" - "", clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } if (xmldb_create(h, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "Internal error" - "%s" - "", clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -829,13 +755,8 @@ from_client_create_subscription(clicon_handle h, if ((ftype = xml_find_value(x, "type")) != NULL){ /* Only accept xpath as filter type */ if (strcmp(ftype, "xpath") != 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "only xpath filter type supported" - "type" - ""); + if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0) + goto done; goto ok; } } @@ -866,12 +787,8 @@ from_client_debug(clicon_handle h, char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "level" - ""); + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + goto done; goto ok; } level = atoi(valstr); @@ -882,11 +799,276 @@ from_client_debug(clicon_handle h, cprintf(cbret, ""); ok: retval = 0; - //done: + done: return retval; } +/*! Match nacm access operations according to RFC8321 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 + */ +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(mode, access_operations)!=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 RFC8321 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"); + 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 RFC8321 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. * @param[in] arg Client entry (from). * @retval 0 OK @@ -907,7 +1089,10 @@ from_client_msg(clicon_handle h, cbuf *cbret = NULL; /* return message */ int pid; int ret; + char *username; + char *nacm_mode; + clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; /* Return netconf message. Should be filled in by the dispatch(sub) functions * as wither rpc-error or by positive response. @@ -917,28 +1102,28 @@ from_client_msg(clicon_handle h, goto done; } if (clicon_msg_decode(msg, &xt) < 0){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "rpc expected" - "Not recognized" - ""); + if (netconf_malformed_message(cbret, "XML parse error")< 0) + goto done; goto reply; } if ((x = xpath_first(xt, "/rpc")) == NULL){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "rpc expected" - "Not recognized" - ""); + if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) + goto done; goto reply; } xe = NULL; + username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { name = xml_name(xe); + clicon_debug(1, "%s name:%s", __FUNCTION__, name); + /* Make NACM access control if enabled as "internal"*/ + nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); + if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){ + if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0) + goto done; + if (!ret) + goto reply; + } if (strcmp(name, "get-config") == 0){ if (from_client_get_config(h, xe, cbret) <0) goto done; @@ -977,12 +1162,8 @@ from_client_msg(clicon_handle h, } else if (strcmp(name, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "source" - ""); + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; goto reply; } if (from_client_validate(h, db, cbret) < 0) @@ -1006,36 +1187,26 @@ from_client_msg(clicon_handle h, } else{ clicon_err_reset(); - if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error:%s" - "", clicon_err_reason); - clicon_log(LOG_NOTICE, "%s Error in backend_rpc_call:%s", __FUNCTION__, xml_name(xe)); + if ((ret = rpc_callback_call(h, xe, cbret, ce)) < 0){ + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; + clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); goto reply; /* Dont quit here on user callbacks */ } - if (ret == 0) /* not handled by callback */ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "%s" - "Not recognized" - "", - name); + if (ret == 0){ /* not handled by callback */ + if (netconf_operation_failed(cbret, "application", "Callback not recognized")< 0) + goto done; + goto reply; + } } } reply: if (cbuf_len(cbret) == 0) - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error %s" - "",clicon_err_reason); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); + /* XXX problem here is that cbret has not been parsed so may contain + parse errors */ if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){ switch (errno){ case EPIPE: @@ -1055,7 +1226,8 @@ from_client_msg(clicon_handle h, } // ok: retval = 0; - done: + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xt) xml_free(xt); if (cbret) @@ -1085,6 +1257,7 @@ from_client(int s, clicon_handle h = ce->ce_handle; int eof; + clicon_debug(1, "%s", __FUNCTION__); // assert(s == ce->ce_s); if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0) goto done; diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 73753b63..6df3852f 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -239,13 +239,20 @@ candidate_commit(clicon_handle h, if (plugin_transaction_commit(h, td) < 0) goto done; - /* 8. Success: Copy candidate to running */ + /* 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; + /* 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 ? */ @@ -275,35 +282,32 @@ from_client_commit(clicon_handle h, { int retval = -1; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ /* Check if target locked by other client */ piddb = xmldb_islocked(h, "running"); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */ clicon_debug(1, "Commit candidate failed"); - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "%s" - "", - clicon_err_reason); + if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } /* from_client_commit */ @@ -321,33 +325,31 @@ from_client_discard_changes(clicon_handle h, { int retval = -1; int piddb; - + cbuf *cbx = NULL; /* Assist cbuf */ + /* Check if target locked by other client */ piddb = xmldb_islocked(h, "candidate"); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_copy(h, "running", "candidate") < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } @@ -367,11 +369,8 @@ from_client_validate(clicon_handle h, transaction_data_t *td = NULL; if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - ""); + if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) + goto done; goto ok; } clicon_debug(1, "Validate %s", db); @@ -383,15 +382,14 @@ from_client_validate(clicon_handle h, if (validate_common(h, db, td) < 0){ clicon_debug(1, "Validate %s failed", db); /* XXX: candidate_validate should have proper error handling */ - cprintf(cbret, "" - "missing-attribute" - "protocol" - "error" - "%s" - "", - clicon_err_reason); + 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; cprintf(cbret, ""); ok: retval = 0; diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 8eb3e073..676cc50c 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -52,4 +52,8 @@ 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 31cb39c2..9babb24e 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -86,9 +86,9 @@ backend_terminate(clicon_handle h) clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); - plugin_finish(h); + clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ - backend_rpc_cb_delete_all(); + rpc_callback_delete_all(); if (pidfile) unlink(pidfile); if (sockpath) @@ -174,8 +174,8 @@ db_merge(clicon_handle h, /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; - /* Merge xml into db2. WIthout commit */ - if (xmldb_put(h, (char*)db2, OP_MERGE, xt) < 0) + /* Merge xml into db2. Without commit */ + if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) goto done; retval = 0; done: @@ -254,12 +254,65 @@ plugin_start_useroptions(clicon_handle h, tmp = *(argv-1); *(argv-1) = argv0; - if (plugin_start_argv(h, argc+1, argv-1) < 0) + if (clixon_plugin_start(h, argc+1, argv-1) < 0) return -1; *(argv-1) = tmp; return 0; } +/*! Load external NACM file + */ +static int +nacm_load_external(clicon_handle h) +{ + int retval = -1; + char *filename; /* NACM config file */ + yang_spec *yspec = NULL; + cxobj *xt = NULL; + struct stat st; + FILE *f = NULL; + int fd; + + filename = clicon_option_str(h, "CLICON_NACM_FILE"); + if (filename == NULL || strlen(filename)==0){ + clicon_err(OE_UNIX, errno, "CLICON_NACM_FILE not set in NACM external mode"); + goto done; + } + if (stat(filename, &st) < 0){ + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; + } + if (!S_ISREG(st.st_mode)){ + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL) { + clicon_err(OE_UNIX, errno, "configure file: %s", filename); + return -1; + } + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf-acm", NULL, yspec) < 0) + goto done; + fd = fileno(f); + /* Read configfile */ + if (xml_parse_file(fd, "", yspec, &xt) < 0) + goto done; + if (xt == NULL){ + clicon_err(OE_XML, 0, "No xml tree in %s", filename); + goto done; + } + if (backend_nacm_list_set(h, xt) < 0) + goto done; + retval = 0; + done: + if (yspec) /* The clixon yang-spec is not used after this */ + yspec_free(yspec); + if (f) + fclose(f); + return retval; +} + /*! Merge xml in filename into database */ static int @@ -283,7 +336,7 @@ load_extraxml(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) + if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) goto done; retval = 0; done: @@ -306,7 +359,7 @@ startup_mode_none(clicon_handle h) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; retval = 0; done: @@ -328,7 +381,7 @@ startup_mode_init(clicon_handle h) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; retval = 0; done: @@ -364,13 +417,13 @@ startup_mode_running(clicon_handle h, if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) @@ -437,13 +490,13 @@ startup_mode_startup(clicon_handle h, if (xmldb_create(h, "startup") < 0) /* diff */ return -1; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) @@ -475,7 +528,8 @@ startup_mode_startup(clicon_handle h, } int -main(int argc, char **argv) +main(int argc, + char **argv) { int retval = -1; char c; @@ -497,14 +551,13 @@ main(int argc, char **argv) int xml_cache; int xml_pretty; char *xml_format; - + char *nacm_mode; + /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); /* Initiate CLICON handle */ if ((h = backend_handle_init()) == NULL) return -1; - if (backend_plugin_init(h) != 0) - return -1; foreground = 0; once = 0; zap = 0; @@ -552,7 +605,12 @@ main(int argc, char **argv) usage(argv[0], h); return -1; } - + /* External NACM file? */ + nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); + if (nacm_mode && strcmp(nacm_mode, "external") == 0) + if (nacm_load_external(h) < 0) + goto done; + /* Now run through the operational args */ opterr = 1; optind = 1; @@ -703,43 +761,41 @@ main(int argc, char **argv) if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0) goto done; - /* If startup mode is not defined, eg via OPTION or -s, assume old method */ + /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.\n"); goto done; } - else { - /* Init running db if it is not there - */ - if (xmldb_exists(h, "running") != 1) - if (xmldb_create(h, "running") < 0) - return -1; - switch (startup_mode){ - case SM_NONE: - if (startup_mode_none(h) < 0) - goto done; - break; - case SM_INIT: /* -I */ - if (startup_mode_init(h) < 0) - goto done; - break; - case SM_RUNNING: /* -CIr */ - if (startup_mode_running(h, extraxml_file) < 0) - goto done; - break; - case SM_STARTUP: /* startup configuration */ - if (startup_mode_startup(h, extraxml_file) < 0) - goto done; - break; - } - /* Initiate the shared candidate. */ - if (xmldb_copy(h, "running", "candidate") < 0) + /* Init running db if it is not there + */ + if (xmldb_exists(h, "running") != 1) + if (xmldb_create(h, "running") < 0) + return -1; + switch (startup_mode){ + case SM_NONE: + if (startup_mode_none(h) < 0) goto done; - /* Call plugin_start with user -- options */ - if (plugin_start_useroptions(h, argv0, argc, argv) <0) + break; + case SM_INIT: /* -I */ + if (startup_mode_init(h) < 0) goto done; + break; + case SM_RUNNING: /* -CIr */ + if (startup_mode_running(h, extraxml_file) < 0) + goto done; + break; + case SM_STARTUP: /* startup configuration */ + if (startup_mode_startup(h, extraxml_file) < 0) + goto done; + break; } + /* Initiate the shared candidate. */ + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + /* Call backend plugin_start with user -- options */ + if (plugin_start_useroptions(h, argv0, argc, argv) <0) + goto done; if (once) goto done; diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index fe3e5a6a..e69c21fa 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -64,404 +64,139 @@ #include "backend_plugin.h" #include "backend_commit.h" -/* - * Types - */ -/* Following are specific to backend. For common see clicon_plugin.h - * @note the following should match the prototypes in clixon_backend.h - */ -#define PLUGIN_RESET "plugin_reset" -typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system status */ - -/*! Plugin callback, if defined called to get state data from plugin - * @param[in] h Clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in] xtop XML tree, on entry. - * @retval 0 OK - * @retval -1 Error - * @see xmldb_get - */ -#define PLUGIN_STATEDATA "plugin_statedata" -typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); - -#define PLUGIN_TRANS_BEGIN "transaction_begin" -#define PLUGIN_TRANS_VALIDATE "transaction_validate" -#define PLUGIN_TRANS_COMPLETE "transaction_complete" -#define PLUGIN_TRANS_COMMIT "transaction_commit" -#define PLUGIN_TRANS_END "transaction_end" -#define PLUGIN_TRANS_ABORT "transaction_abort" - - -typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */ - -/* Backend (config) plugins */ -struct plugin { - char p_name[PATH_MAX]; /* Plugin name */ - void *p_handle; /* Dynamic object handle */ - plginit_t *p_init; /* Init */ - plgstart_t *p_start; /* Start */ - plgexit_t *p_exit; /* Exit */ - plgreset_t *p_reset; /* Reset state */ - plgstatedata_t *p_statedata; /* State-data callback */ - trans_cb_t *p_trans_begin; /* Transaction start */ - trans_cb_t *p_trans_validate; /* Transaction validation */ - trans_cb_t *p_trans_complete; /* Transaction validation complete */ - trans_cb_t *p_trans_commit; /* Transaction commit */ - trans_cb_t *p_trans_end; /* Transaction completed */ - trans_cb_t *p_trans_abort; /* Transaction aborted */ - -}; - -/* - * Local variables - */ -static int _nplugins = 0; -static struct plugin *_plugins = NULL; - -/*! Find a plugin by name and return the dlsym handl - * Used by libclicon code to find callback funcctions in plugins. - * @param[in] h Clicon handle - * @param[in] h Name of plugin - * @retval handle Plugin handle if found - * @retval NULL Not found - */ -static void * -config_find_plugin(clicon_handle h, - char *name) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (strcmp(p->p_name, name) == 0) - return p->p_handle; - } - return NULL; -} - -/*! Initialize plugin code (not the plugins themselves) - * @param[in] h Clicon handle - * @retval 0 OK - * @retval -1 Error - */ -int -backend_plugin_init(clicon_handle h) -{ - find_plugin_t *fp = config_find_plugin; - clicon_hash_t *data = clicon_data(h); - - /* Register CLICON_FIND_PLUGIN in data hash */ - if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) { - clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN"); - return -1; - } - return 0; -} - -/*! Unload a plugin - * @param[in] h Clicon handle - * @param[in] plg Plugin structure - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_unload(clicon_handle h, - struct plugin *plg) -{ - int retval=-1; - char *error; - - /* Call exit function is it exists */ - if (plg->p_exit) - plg->p_exit(h); - - dlerror(); /* Clear any existing error */ - if (dlclose(plg->p_handle) != 0) { - error = (char*)dlerror(); - clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error"); - goto done; - /* Just report */ - } - else - clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name); - retval = 0; - done: - return retval; -} - - -/*! Load a dynamic plugin and call its init-function - * @param[in] h Clicon handle - * @param[in] file The plugin (.so) to load - * @param[in] dlflags Arguments to dlopen(3) - * @retval plugin Plugin struct - * @retval NULL Error - */ -static struct plugin * -backend_plugin_load (clicon_handle h, - char *file, - int dlflags) -{ - void *handle; - char *name; - struct plugin *new = NULL; - - if ((handle = plugin_load(h, file, dlflags)) == NULL) - goto done; - if ((new = malloc(sizeof(*new))) == NULL) { - clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno)); - dlclose(handle); - return NULL; - } - memset(new, 0, sizeof(*new)); - name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; - clicon_debug(2, "Loading plugin '%s'.", name); - snprintf(new->p_name, sizeof(new->p_name), "%*s", - (int)strlen(name)-2, name); - new->p_handle = handle; - if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_START); - if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_EXIT); - if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_RESET); - if ((new->p_statedata = dlsym(handle, PLUGIN_STATEDATA)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_STATEDATA); - if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN); - if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE); - if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE); - if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT); - if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END); - if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT); - clicon_debug(2, "Plugin '%s' loaded.\n", name); - done: - return new; -} - -/*! Request plugins to reset system state - * The system 'state' should be the same as the contents of running_db - * @param[in] h Clicon handle - * @param[in] dbname Name of database - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_reset_state(clicon_handle h, - const char *db) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_reset) { - clicon_debug(1, "Calling plugin_reset() for %s\n", - p->p_name); - if (((p->p_reset)(h, db)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Call plugin_start in all plugins - * @param[in] h Clicon handle - * @param[in] argc Command-line arguments - * @param[in] argv Command-line arguments - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_start_argv(clicon_handle h, - int argc, - char **argv) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_start) { - optind = 0; - if (((p->p_start)(h, argc, argv)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Append plugin to list - * @param[in] p Plugin - * @retval 0 OK - * @retval -1 Error - */ -static int -plugin_append(struct plugin *p) -{ - struct plugin *new; - - if ((new = realloc(_plugins, (_nplugins+1) * sizeof (*p))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - return -1; - } - - memset (&new[_nplugins], 0, sizeof(new[_nplugins])); - memcpy (&new[_nplugins], p, sizeof(new[_nplugins])); - _plugins = new; - _nplugins++; - - return 0; -} - -/*! Load backend plugins found in a directory - * The plugins must have the '.so' suffix - * @param[in] h Clicon handle - * @param[in] dir Backend plugin directory - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_load_dir(clicon_handle h, - const char *dir) -{ - int retval = -1; - int i; - int np = 0; - int ndp; - struct stat st; - char filename[MAXPATHLEN]; - struct dirent *dp = NULL; - struct plugin *new; - struct plugin *p = NULL; - char master[MAXPATHLEN]; - char *master_plugin; - - /* Format master plugin path */ - if ((master_plugin = clicon_master_plugin(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); - goto quit; - } - snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin); - - /* Allocate plugin group object */ - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* reset num plugins */ - np = 0; - - /* Master plugin must be loaded first if it exists. */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master); - if (stat(filename, &st) == 0) { - clicon_debug(1, "Loading master plugin '%.*s' ...", - (int)strlen(filename), filename); - - new = backend_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL); - if (new == NULL) - goto quit; - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* Now load the rest. Note plugins is the global variable */ - for (i = 0; i < ndp; i++) { - if (strcmp(dp[i].d_name, master) == 0) - continue; /* Skip master now */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename); - new = backend_plugin_load(h, filename, RTLD_NOW); - if (new == NULL) - goto quit; - /* Append to 'plugins' */ - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* All good. */ - retval = 0; - -quit: - if (retval != 0) { - /* XXX p is always NULL */ - if (_plugins) { - while (--np >= 0){ - if ((p = &_plugins[np]) == NULL) - continue; - backend_plugin_unload(h, p); - free(p); - } - free(_plugins); - _plugins=0; - } - } - if (dp) - free(dp); - return retval; -} - - /*! Load a plugin group. * @param[in] h Clicon handle * @retval 0 OK * @retval -1 Error */ int -plugin_initiate(clicon_handle h) +backend_plugin_initiate(clicon_handle h) { char *dir; - /* First load CLICON system plugins */ - if (backend_plugin_load_dir(h, CLIXON_BACKEND_SYSDIR) < 0) - return -1; - - /* Then load application plugins */ - dir = clicon_backend_dir(h); - /* If backend directory, load the backend plugisn */ - if (dir && backend_plugin_load_dir(h, dir) < 0) - return -1; - - return 0; + /* Load application plugins */ + if ((dir = clicon_backend_dir(h)) == NULL) + return 0; + return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, + clicon_option_str(h, "CLICON_BACKEND_REGEXP")); } -/*! Unload and deallocate all backend plugins +/*! Request plugins to reset system state + * The system 'state' should be the same as the contents of running_db * @param[in] h Clicon handle + * @param[in] db Name of database * @retval 0 OK * @retval -1 Error */ int -plugin_finish(clicon_handle h) +clixon_plugin_reset(clicon_handle h, + char *db) { - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - backend_plugin_unload(h, p); + clixon_plugin *cp = NULL; + plgreset_t *resetfn; /* Plugin auth */ + int retval = 1; + + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((resetfn = cp->cp_api.ca_reset) == NULL) + continue; + if ((retval = resetfn(h, db)) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + break; } - if (_plugins){ - free(_plugins); - _plugins = NULL; - } - _nplugins = 0; - return 0; + return retval; } - + +/*! Go through all backend statedata callbacks and collect state data + * This is internal system call, plugin is invoked (does not call) this function + * Backend plugins can register + * @param[in] h clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in,out] xtop State XML tree is merged with existing tree. + * @retval -1 Error + * @retval 0 OK + * @retval 1 Statedata callback failed + * @note xtop can be replaced + */ +int +clixon_plugin_statedata(clicon_handle h, + char *xpath, + cxobj **xtop) +{ + int retval = -1; + int i; + cxobj *x = NULL; + yang_spec *yspec; + cxobj **xvec = NULL; + size_t xlen; + cxobj *xc; + clixon_plugin *cp = NULL; + plgstatedata_t *fn; /* Plugin statedata fn */ + char *reason = NULL; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (*xtop==NULL){ + clicon_err(OE_CFG, ENOENT, "XML tree expected"); + goto done; + } + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_statedata) == NULL) + continue; + if ((x = xml_new("config", NULL, NULL)) == NULL) + goto done; + if (fn(h, xpath, x) < 0){ + retval = 1; + goto done; /* Dont quit here on user callbacks */ + } + if (xml_merge(*xtop, x, yspec, &reason) < 0) + goto done; + if (reason){ + while ((xc = xml_child_i(*xtop, 0)) != NULL) + xml_purge(xc); + if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0) + goto done; + goto ok; + } + if (x){ + xml_free(x); + x = NULL; + } + } + /* Code complex to filter out anything that is outside of xpath */ + if (xpath_vec(*xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; ip_trans_begin) - if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN); + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_begin) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_begin callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); break; - } - + } } return retval; } @@ -540,20 +274,18 @@ plugin_transaction_validate(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - struct plugin *p; - - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_validate) - if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE); - - break; - } + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_validate) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_validate callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + break; + } } return retval; } @@ -570,20 +302,20 @@ int plugin_transaction_complete(clicon_handle h, transaction_data_t *td) { - int i; int retval = 0; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_complete) - if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE); - - break; - } + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_complete) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_complete callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + + break; + } } return retval; } @@ -604,9 +336,9 @@ plugin_transaction_revert(clicon_handle h, { int retval = 0; transaction_data_t tr; /* revert transaction */ - int i; - struct plugin *p; - + clixon_plugin *cp = NULL; + trans_cb_t *fn; + /* Create a new reversed transaction from the original where src and target are swapped */ memcpy(&tr, td, sizeof(tr)); @@ -620,14 +352,14 @@ plugin_transaction_revert(clicon_handle h, tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; - for (i = nr-1; i>=0; i--){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){ - clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed", - p->p_name, PLUGIN_TRANS_COMMIT); + while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { + if ((fn = cp->cp_api.ca_trans_commit) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed", + __FUNCTION__, cp->cp_name); break; - } + } } return retval; /* ignore errors */ } @@ -646,20 +378,22 @@ plugin_transaction_commit(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; + int i=0; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT); - /* Make an effort to revert transaction */ - plugin_transaction_revert(h, td, i); - break; - } + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + i++; + if ((fn = cp->cp_api.ca_trans_commit) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + /* Make an effort to revert transaction */ + plugin_transaction_revert(h, td, i-1); + break; + } } return retval; } @@ -675,19 +409,18 @@ plugin_transaction_end(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_end) - if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_END); - - break; - } + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_end) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_end callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + break; + } } return retval; } @@ -703,94 +436,14 @@ plugin_transaction_abort(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_abort) - (p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */ + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_abort) == NULL) + continue; + fn(h, (transaction_data)td); /* dont abort on error */ } return retval; } -/*---------------------------------------------------------------------- - * Backend state data callbacks - */ - -/*! Go through all backend statedata callbacks and collect state data - * This is internal system call, plugin is invoked (does not call) this function - * Backend plugins can register - * @param[in] h clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in,out] xml XML tree. - * @retval -1 Error - * @retval 0 OK - * @retval 1 Statedata callback failed - */ -int -backend_statedata_call(clicon_handle h, - char *xpath, - cxobj *xtop) -{ - int retval = -1; - struct plugin *p; - int i; - cxobj *x = NULL; - yang_spec *yspec; - cxobj **xvec = NULL; - size_t xlen; - - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if (xtop==NULL){ - clicon_err(OE_CFG, ENOENT, "XML tree expected"); - goto done; - } - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_statedata) { - if ((x = xml_new("config", NULL, NULL)) == NULL) - goto done; - if ((p->p_statedata)(h, xpath, x) < 0){ - retval = 1; - goto done; /* Dont quit here on user callbacks */ - } - if (xml_merge(xtop, x, yspec) < 0) - goto done; - if (x){ - xml_free(x); - x = NULL; - } - } - } - /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) - goto done; - - /* If vectors are specified then mark the nodes found and - * then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i #include -/*! Clicon Backend plugin callbacks: use these in your backend plugin code - */ - -/*! Called when plugin loaded. Only mandadory callback. All others optional - * @see plginit_t - */ -int plugin_init(clicon_handle h); - -/* Called when backend started with cmd-line arguments from daemon call. - * @see plgstart_t - */ -int plugin_start(clicon_handle h, int argc, char **argv); - -/* Called just before plugin unloaded. - * @see plgexit_t - */ -int plugin_exit(clicon_handle h); - -/*! Reset system state to original state. Eg at reboot before running thru config. - * @see plgreset_t - */ -int plugin_reset(clicon_handle h, const char *db); - -/*! Retreive statedata, add statedata to XML tree - * @see plgstatedata_ t - */ -int plugin_statedata(clicon_handle h, char *xpath, cxobj *xtop); - -/*! Called before a commit/validate sequence begins. Eg setup state before commit - * @see trans_cb_t - */ -int transaction_begin(clicon_handle h, transaction_data td); - -/*! Validate. - * @see trans_cb_t - */ -int transaction_validate(clicon_handle h, transaction_data td); - -/* Called after a validation completed succesfully (but before commit). - * @see trans_cb_t - */ -int transaction_complete(clicon_handle h, transaction_data td); - -/* Commit. - * @see trans_cb_t - */ -int transaction_commit(clicon_handle h, transaction_data td); - -/* Called after a commit sequence completed succesfully. - * @see trans_cb_t - */ -int transaction_end(clicon_handle h, transaction_data td); - -/* Called if commit or validate sequence fails. After eventual rollback. - * @see trans_cb_t - */ -int transaction_abort(clicon_handle h, transaction_data td); - #endif /* _CLIXON_BACKEND_H_ */ diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index aeba8a23..dfb9461a 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -52,6 +52,7 @@ #include #include #include +#include /* cligen */ #include @@ -89,6 +90,7 @@ struct backend_handle { struct client_entry *bh_ce_list; /* The client list */ int bh_ce_nr; /* Number of clients, just increment */ struct handle_subscription *bh_subscription; /* Event subscription list */ + cxobj *bh_nacm; /* NACM external struct */ }; /*! Creates and returns a clicon config handle for other CLICON API calls @@ -105,11 +107,14 @@ backend_handle_init(void) int backend_handle_exit(clicon_handle h) { + struct backend_handle *bh = handle(h); struct client_entry *ce; /* only delete client structs, not close sockets, etc, see backend_client_rm */ while ((ce = backend_client_list(h)) != NULL) backend_client_delete(h, ce); + if (bh->bh_nacm) + xml_free(bh->bh_nacm); clicon_handle_exit(h); /* frees h and options */ return 0; } @@ -430,110 +435,22 @@ subscription_each(clicon_handle h, return hs; } -/*-------------------------------------------------------------------- - * Backend netconf rpc callbacks - */ -typedef struct { - qelem_t rc_qelem; /* List header */ - backend_rpc_cb rc_callback; /* RPC Callback */ - void *rc_arg; /* Application specific argument to cb */ - char *rc_tag; /* Xml tag when matched, callback called */ -} backend_rpc_cb_entry; - -/* List of backend rpc callback entries */ -static backend_rpc_cb_entry *rpc_cb_list = NULL; - -/*! Register netconf backend rpc callback - * Called from plugin to register a callback for a specific netconf XML tag. - * - * @param[in] h clicon handle - * @param[in] cb, Callback called - * @param[in] arg, Arg to send to callback - * @param[in] tag Xml tag when callback is made - * @see backend_rpc_cb_call - */ int -backend_rpc_cb_register(clicon_handle h, - backend_rpc_cb cb, - void *arg, - char *tag) +backend_nacm_list_set(clicon_handle h, + cxobj *xnacm) { - backend_rpc_cb_entry *rc; + struct backend_handle *bh = handle(h); - if ((rc = malloc(sizeof(backend_rpc_cb_entry))) == NULL) { - clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); - goto catch; - } - memset (rc, 0, sizeof (*rc)); - rc->rc_callback = cb; - rc->rc_arg = arg; - rc->rc_tag = strdup(tag); /* XXX strdup memleak */ - INSQ(rc, rpc_cb_list); - return 0; -catch: - if (rc){ - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } - return -1; -} - -/*! Search netconf backend callbacks and invoke if match - * This is internal system call, plugin is invoked (does not call) this functino - * @param[in] h clicon handle - * @param[in] xe Sub-tree (under xorig) at child of rpc: . - * @param[in] ce Client (session) entry - * @param[out] cbret Return XML, error or OK as cbuf - * - * @retval -1 Error - * @retval 0 OK, not found handler. - * @retval 1 OK, handler called - * @see backend_rpc_cb_register - */ -int -backend_rpc_cb_call(clicon_handle h, - cxobj *xe, - struct client_entry *ce, - cbuf *cbret) -{ - backend_rpc_cb_entry *rc; - int retval = -1; - - if (rpc_cb_list == NULL) - return 0; - rc = rpc_cb_list; - do { - if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ - if ((retval = rc->rc_callback(h, xe, ce, cbret, rc->rc_arg)) < 0){ - clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); - goto done; - } - else{ - retval = 1; /* handled */ - goto done; - } - } - rc = NEXTQ(backend_rpc_cb_entry *, rc); - } while (rc != rpc_cb_list); - retval = 0; - done: - return retval; -} - -/*! Delete all state data callbacks. - */ -int -backend_rpc_cb_delete_all(void) -{ - backend_rpc_cb_entry *rc; - - while((rc = rpc_cb_list) != NULL) { - DELQ(rc, rpc_cb_list, backend_rpc_cb_entry *); - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } + if (bh->bh_nacm) + xml_free(bh->bh_nacm); + bh->bh_nacm = xnacm; return 0; } +cxobj * +backend_nacm_list_get(clicon_handle h) +{ + struct backend_handle *bh = handle(h); + + return bh->bh_nacm; +} diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index 2edb61b8..3855a14b 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -43,30 +43,6 @@ /* * Types */ -struct client_entry; -typedef int (*backend_rpc_cb)( - clicon_handle h, /* CLicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret,/* Reply eg ... */ - void *arg /* Argument given at register */ -); -typedef backend_rpc_cb backend_netconf_cb_t; /* XXX backward compat */ - - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - -/* - * Log for netconf notify function (config_client.c) - */ -int backend_notify(clicon_handle h, char *stream, int level, char *txt); -int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); - /* subscription callback */ typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg); @@ -82,6 +58,14 @@ struct handle_subscription{ void *hs_arg; /* Callback argument */ }; +/* + * Prototypes + */ +/* Log for netconf notify function (config_client.c) */ +int backend_notify(clicon_handle h, char *stream, int level, char *txt); +int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); + + struct handle_subscription *subscription_add(clicon_handle h, char *stream, enum format_enum format, char *filter, subscription_fn_t fn, void *arg); @@ -92,14 +76,4 @@ int subscription_delete(clicon_handle h, char *stream, struct handle_subscription *subscription_each(clicon_handle h, struct handle_subscription *hprev); -/* XXX backward compat */ -#define backend_netconf_register_callback(a,b,c,d) backend_rpc_cb_register(a,b,c,d) -int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg, - char *tag); - -int backend_rpc_cb_call(clicon_handle h, cxobj *xe, struct client_entry *ce, - cbuf *cbret); - -int backend_rpc_cb_delete_all(void); - #endif /* _CLIXON_BACKEND_HANDLE_H_ */ diff --git a/apps/backend/clixon_backend_transaction.c b/apps/backend/clixon_backend_transaction.c index 18b5c807..173ba01c 100644 --- a/apps/backend/clixon_backend_transaction.c +++ b/apps/backend/clixon_backend_transaction.c @@ -50,6 +50,7 @@ #include #include #include +#include /* cligen */ #include diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index 2647a45c..a290c5f1 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -41,21 +41,12 @@ #define _CLIXON_BACKEND_TRANSACTION_H_ /* - * Types + * Prototypes */ - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - /* Transaction callback data accessors for client plugins * (defined in config_dbdep.c) * @see transaction_data_t internal structure */ -typedef void *transaction_data; uint64_t transaction_id(transaction_data td); void *transaction_arg(transaction_data td); cxobj *transaction_src(transaction_data td); diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index 359038b0..a55918d9 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -36,6 +36,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -56,8 +57,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ # Use this clixon lib for linking CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) -# Location of system plugins -CLIXON_CLI_SYSDIR = $(libdir)/clixon/plugins/cli # For dependency. A little strange that we rely on it being built in the src dir # even though it may exist in $(libdir). But the new version may not have been installed yet. @@ -68,22 +67,33 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_L CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ +# Name of application APPL = clixon_cli -SRC = cli_main.c -OBJS = $(SRC:.c=.o) +# Not accessible from plugin +APPSRC = cli_main.c +APPSRC += cli_generate.c +APPOBJ = $(APPSRC:.c=.o) + +# Accessible from plugin +LIBSRC = cli_common.c +LIBSRC += cli_show.c +LIBSRC += cli_handle.c +LIBSRC += cli_plugin.c +LIBOBJ = $(LIBSRC:.c=.o) + +# Name of lib MYNAME = clixon_cli MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) -LIBSRC = cli_plugin.c cli_common.c cli_show.c cli_handle.c cli_generate.c -LIBOBJS = $(LIBSRC:.c=.o) + all: $(MYLIB) $(APPL) test clean: - rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) + rm -f $(LIBOBJ) $(APPOBJ) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend test test.c @@ -93,19 +103,19 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_cli.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_cli.so - install -d $(DESTDIR)$(libdir)/clixon/plugins/cli + install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/cli install-include: clixon_cli.h clixon_cli_api.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(bindir)/$(APPL) @@ -116,7 +126,7 @@ uninstall: .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CLI_SYSDIR=\"$(CLIXON_CLI_SYSDIR)\" $(CFLAGS) -c $< + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< # Just link test programs test.c : @@ -125,14 +135,14 @@ test.c : test: test.c $(LIBOBJ) $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(APPL): $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ +$(APPL): $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(MYLIB) : $(LIBOBJS) +$(MYLIB) : $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) - $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) else - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) endif # link-name is needed for application linking, eg for clixon_cli and clixon_config @@ -144,7 +154,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index c0266ab6..4979f412 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; @@ -829,10 +829,19 @@ save_config_file(clicon_handle h, filename = cv_string_get(cv); if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) goto done; + if (xt == NULL){ + clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */ + goto done; + } if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } + /* get-config returns a tree. Save as tree so it can be used + * as data-store. + */ + if (xml_name_set(xt, "config") < 0) + goto done; if ((f = fopen(filename, "w")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); goto done; diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 023b531d..67fa9cec 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -238,7 +238,6 @@ yang2cli_var_sub(clicon_handle h, 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 */ diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c index 10762a41..1690a143 100644 --- a/apps/cli/cli_handle.c +++ b/apps/cli/cli_handle.c @@ -172,12 +172,22 @@ cli_parse_file(clicon_handle h, } int -cli_susp_hook(clicon_handle h, cli_susphook_t *fn) +cli_susp_hook(clicon_handle h, + cligen_susp_cb_t *fn) { cligen_handle ch = cligen(h); /* This assume first arg of fn can be treated as void* */ - return cligen_susp_hook(ch, (cligen_susp_cb_t*)fn); + return cligen_susp_hook(ch, fn); +} +int +cli_interrupt_hook(clicon_handle h, + cligen_interrupt_cb_t *fn) +{ + cligen_handle ch = cligen(h); + + /* This assume first arg of fn can be treated as void* */ + return cligen_interrupt_hook(ch, fn); } char * diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h index 273d40e8..bbf66742 100644 --- a/apps/cli/cli_handle.h +++ b/apps/cli/cli_handle.h @@ -39,7 +39,7 @@ /* * Prototypes - * Internal prototypes. For exported functions see clicon_cli_api.h + * Internal prototypes. For exported functions see clixon_cli_api.h */ int cli_parse_file(clicon_handle h, FILE *f, @@ -47,7 +47,9 @@ int cli_parse_file(clicon_handle h, parse_tree *pt, cvec *globals); -int cli_susp_hook(clicon_handle h, cli_susphook_t *fn); +int cli_susp_hook(clicon_handle h, cligen_susp_cb_t *fn); + +int cli_interrupt_hook(clicon_handle h, cligen_interrupt_cb_t *fn); char *cli_nomatch(clicon_handle h); @@ -56,8 +58,8 @@ int cli_prompt_set(clicon_handle h, char *prompt); int cli_logsyntax_set(clicon_handle h, int status); /* Internal functions for handling cli groups */ - cli_syntax_t *cli_syntax(clicon_handle h); + int cli_syntax_set(clicon_handle h, cli_syntax_t *stx); #endif /* _CLI_HANDLE_H_ */ diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 8f691256..f1d9678e 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -243,17 +244,24 @@ main(int argc, char **argv) char *restarg = NULL; /* what remains after options */ int dump_configfile_xml = 0; yang_spec *yspec; + struct passwd *pw; /* Defaults */ + once = 0; + /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); /* Initiate CLICON handle */ if ((h = cli_handle_init()) == NULL) goto done; - - if (cli_plugin_init(h) != 0) + /* Set username to clicon handle. Use in all communication to backend */ + if ((pw = getpwuid(getuid())) == NULL){ + clicon_err(OE_UNIX, errno, "getpwuid"); goto done; - once = 0; + } + if (clicon_username_set(h, pw->pw_name) < 0) + goto done; + cligen_comment_set(cli_cligen(h), '#'); /* Default to handle #! clicon_cli scripts */ /* @@ -460,7 +468,7 @@ main(int argc, char **argv) */ tmp = *(argv-1); *(argv-1) = argv0; - cli_plugin_start(h, argc+1, argv-1); + clixon_plugin_start(h, argc+1, argv-1); *(argv-1) = tmp; cligen_line_scrolling_set(cli_cligen(h), clicon_option_int(h,"CLICON_CLI_LINESCROLLING")); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index f2da6b78..04fc027c 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -66,15 +66,6 @@ #include "cli_plugin.h" #include "cli_handle.h" - -/*! Name of master plugin functions - * More in clicon_plugin.h - * @note not really used consider documenting or remove - */ -#define PLUGIN_PROMPT_HOOK "plugin_prompt_hook" -#define PLUGIN_PARSE_HOOK "plugin_parse_hook" -#define PLUGIN_SUSP_HOOK "plugin_susp_hook" - /* * * CLI PLUGIN INTERFACE, INTERNAL SECTION @@ -84,7 +75,9 @@ /*! Find syntax mode named 'mode'. Create if specified */ static cli_syntaxmode_t * -syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) +syntax_mode_find(cli_syntax_t *stx, + const char *mode, + int create) { cli_syntaxmode_t *m; @@ -113,40 +106,26 @@ syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) return m; } -/*! Find plugin by name - */ -static struct cli_plugin * -plugin_find_cli(cli_syntax_t *stx, char *plgnam) -{ - struct cli_plugin *p; - - if ((p = stx->stx_plugins) != NULL) - do { - if (strcmp (p->cp_name, plgnam) == 0) - return p; - p = NEXTQ(struct cli_plugin *, p); - } while (p && p != stx->stx_plugins); - - return NULL; -} - /*! Generate parse tree for syntax mode + * @param[in] h Clicon handle + * @param[in] m Syntax mode struct */ static int -gen_parse_tree(clicon_handle h, cli_syntaxmode_t *m) +gen_parse_tree(clicon_handle h, + cli_syntaxmode_t *m) { cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt); return 0; } - /*! Append syntax + * @param[in] h Clicon handle */ static int syntax_append(clicon_handle h, cli_syntax_t *stx, - const char *name, - parse_tree pt) + const char *name, + parse_tree pt) { cli_syntaxmode_t *m; @@ -159,27 +138,18 @@ syntax_append(clicon_handle h, return 0; } -/*! Unload all plugins in a group +/*! Remove all cligen syntax modes + * @param[in] h Clicon handle */ static int cli_syntax_unload(clicon_handle h) { cli_syntax_t *stx = cli_syntax(h); - struct cli_plugin *p; cli_syntaxmode_t *m; if (stx == NULL) return 0; - while (stx->stx_nplugins > 0) { - p = stx->stx_plugins; - plugin_unload(h, p->cp_handle); - clicon_debug(1, "DEBUG: Plugin '%s' unloaded.", p->cp_name); - DELQ(p, stx->stx_plugins, struct cli_plugin *); - if (p) - free(p); - stx->stx_nplugins--; - } while (stx->stx_nmodes > 0) { m = stx->stx_modes; DELQ(m, stx->stx_modes, cli_syntaxmode_t *); @@ -239,34 +209,6 @@ clixon_str2fn(char *name, return NULL; } -/*! Load a dynamic plugin object and call it's init-function - * Note 'file' may be destructively modified - * @retval plugin-handle should be freed after use - */ -static plghndl_t -cli_plugin_load(clicon_handle h, - char *file, - int dlflags) -{ - char *name; - plghndl_t handle = NULL; - struct cli_plugin *cp = NULL; - - if ((handle = plugin_load(h, file, dlflags)) == NULL) - goto quit; - if ((cp = malloc(sizeof (struct cli_plugin))) == NULL) { - perror("malloc"); - goto quit; - } - memset (cp, 0, sizeof(*cp)); - name = basename(file); - snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name); - cp->cp_handle = handle; - -quit: - return cp; -} - /*! Append to syntax mode from file * @param[in] h Clixon handle * @param[in] filename Name of file where syntax is specified (in syntax-group dir) @@ -288,7 +230,7 @@ cli_load_syntax(clicon_handle h, char **vec = NULL; int i, nvec; char *plgnam; - struct cli_plugin *p; + clixon_plugin *cp; if (dir) snprintf(filepath, MAXPATHLEN-1, "%s/%s", dir, filename); @@ -318,8 +260,8 @@ cli_load_syntax(clicon_handle h, mode = cvec_find_str(cvv, "CLICON_MODE"); if (plgnam != NULL) { /* Find plugin for callback resolving */ - if ((p = plugin_find_cli (cli_syntax(h), plgnam)) != NULL) - handle = p->cp_handle; + if ((cp = clixon_plugin_find(h, plgnam)) != NULL) + handle = cp->cp_handle; if (handle == NULL){ clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s\n", plgnam, filename, plgnam, @@ -327,7 +269,6 @@ cli_load_syntax(clicon_handle h, goto done; } } - /* Resolve callback names to function pointers. */ if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){ clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)", @@ -345,7 +286,10 @@ cli_load_syntax(clicon_handle h, if ((vec = clicon_strsep(mode, ":", &nvec)) == NULL) goto done; for (i = 0; i < nvec; i++) { - if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) { + if (syntax_append(h, + cli_syntax(h), + vec[i], + pt) < 0) { goto done; } if (prompt) @@ -363,77 +307,7 @@ done: return retval; } -/*! Load plugins within a directory - */ -static int -cli_plugin_load_dir(clicon_handle h, - char *dir, - cli_syntax_t *stx) -{ - int i; - int ndp; - struct dirent *dp = NULL; - char *master_plugin; - char master[MAXPATHLEN]; - char filename[MAXPATHLEN]; - struct cli_plugin *cp; - struct stat st; - int retval = -1; - - - /* Format master plugin path */ - if ((master_plugin = clicon_master_plugin(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); - goto quit; - } - snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin); - - /* Get plugin objects names from plugin directory */ - ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG); - if (ndp < 0) - goto quit; - - /* Load master plugin first */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master); - if (stat(filename, &st) == 0) { - clicon_debug(1, "DEBUG: Loading master plugin '%s'", master); - cp = cli_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL); - if (cp == NULL) - goto quit; - /* Look up certain call-backs in master plugin */ - stx->stx_prompt_hook = - dlsym(cp->cp_handle, PLUGIN_PROMPT_HOOK); - stx->stx_parse_hook = - dlsym(cp->cp_handle, PLUGIN_PARSE_HOOK); - stx->stx_susp_hook = - dlsym(cp->cp_handle, PLUGIN_SUSP_HOOK); - INSQ(cp, stx->stx_plugins); - stx->stx_nplugins++; - } - - /* Load the rest */ - for (i = 0; i < ndp; i++) { - if (strcmp (dp[i].d_name, master) == 0) - continue; /* Skip master now */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%s'", dp[i].d_name); - - if ((cp = cli_plugin_load (h, filename, RTLD_NOW)) == NULL) - goto quit; - INSQ(cp, stx->stx_plugins); - stx->stx_nplugins++; - } - - retval = 0; - - quit: - if (dp) - free(dp); - return retval; -} - - -/*! Load a syntax group. +/*! Load a syntax group. Includes both CLI plugin and CLIgen spec syntax files. * @param[in] h Clicon handle */ int @@ -448,6 +322,9 @@ cli_syntax_load (clicon_handle h) struct dirent *dp = NULL; cli_syntax_t *stx; cli_syntaxmode_t *m; + cligen_susp_cb_t *fns = NULL; + cligen_interrupt_cb_t *fni = NULL; + clixon_plugin *cp; /* Syntax already loaded. XXX should we re-load?? */ if ((stx = cli_syntax(h)) != NULL) @@ -461,59 +338,62 @@ cli_syntax_load (clicon_handle h) /* Allocate plugin group object */ if ((stx = malloc(sizeof(*stx))) == NULL) { clicon_err(OE_UNIX, errno, "malloc"); - goto quit; + goto done; } memset (stx, 0, sizeof (*stx)); /* Zero out all */ cli_syntax_set(h, stx); - /* First load CLICON system plugins. CLIXON_CLI_SYSDIR is defined - in Makefile*/ - if (cli_plugin_load_dir(h, CLIXON_CLI_SYSDIR, stx) < 0) - goto quit; - - /* Then load application plugins */ - if (plugin_dir && cli_plugin_load_dir(h, plugin_dir, stx) < 0) - goto quit; - + /* Load cli plugins */ + if (plugin_dir && + clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir, NULL)< 0) + goto done; if (clispec_file){ if (cli_load_syntax(h, clispec_file, NULL) < 0) - goto quit; + goto done; } if (clispec_dir){ /* load syntaxfiles */ if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0) - goto quit; + goto done; /* Load the rest */ for (i = 0; i < ndp; i++) { clicon_debug(1, "DEBUG: Loading syntax '%.*s'", (int)strlen(dp[i].d_name)-4, dp[i].d_name); if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0) - goto quit; + goto done; } } /* Did we successfully load any syntax modes? */ if (stx->stx_nmodes <= 0) { retval = 0; - goto quit; + goto done; } /* Parse syntax tree for all modes */ m = stx->stx_modes; do { if (gen_parse_tree(h, m) != 0) - goto quit; + goto done; m = NEXTQ(cli_syntaxmode_t *, m); } while (m && m != stx->stx_modes); - - /* Set callbacks into CLIgen */ - cli_susp_hook(h, cli_syntax(h)->stx_susp_hook); + /* Set susp and interrupt callbacks into CLIgen */ + cp = NULL; + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if (fns==NULL && (fns = cp->cp_api.ca_suspend) != NULL) + if (cli_susp_hook(h, fns) < 0) + goto done; + if (fni==NULL && (fni = cp->cp_api.ca_interrupt) != NULL) + if (cli_susp_hook(h, fns) < 0) + goto done; + } /* All good. We can now proudly return a new group */ retval = 0; -quit: +done: if (retval != 0) { + clixon_plugin_exit(h); cli_syntax_unload(h); cli_syntax_set(h, NULL); } @@ -522,34 +402,15 @@ quit: return retval; } -/*! Call plugin_start() in all plugins - */ -int -cli_plugin_start(clicon_handle h, int argc, char **argv) -{ - struct cli_plugin *p; - cli_syntax_t *stx; - plgstart_t *startfun; -// XXX int (*startfun)(clicon_handle, int, char **); - - stx = cli_syntax(h); - - if ((p = stx->stx_plugins) != NULL) - do { - startfun = dlsym(p->cp_handle, PLUGIN_START); - if (dlerror() == NULL) - startfun(h, argc, argv); - p = NEXTQ(struct cli_plugin *, p); - } while (p && p != stx->stx_plugins); - - return 0; -} - -/* +/*! Remove syntax modes and remove syntax + * @param[in] h Clicon handle */ int cli_plugin_finish(clicon_handle h) { + /* Remove all CLI plugins */ + clixon_plugin_exit(h); + /* Remove all cligen syntax modes */ cli_syntax_unload(h); cli_syntax_set(h, NULL); return 0; @@ -558,6 +419,7 @@ cli_plugin_finish(clicon_handle h) /*! Help function to print a meaningful error string. * Sometimes the libraries specify an error string, if so print that. * Otherwise just print 'command error'. + * @param[in] f File handler to write error to. */ int cli_handler_err(FILE *f) @@ -679,15 +541,6 @@ clicon_parse(clicon_handle h, goto done; case CG_NOMATCH: /* no match */ smode = NULL; - if (stx->stx_parse_hook) { - /* Try to find a match in upper modes, a'la IOS. */ - if ((modename = stx->stx_parse_hook(h, cmd, modename)) != NULL) { - if ((smode = syntax_mode_find(stx, modename, 0)) != NULL) - continue; - else - cli_output(f, "Can't find syntax mode '%s'\n", modename); - } - } /* clicon_err(OE_CFG, 0, "CLI syntax error: \"%s\": %s", cmd, cli_nomatch(h));*/ cli_output(f, "CLI syntax error: \"%s\": %s\n", @@ -718,22 +571,30 @@ done: } /*! Read command from CLIgen's cliread() using current syntax mode. + * @param[in] h Clicon handle * @retval string char* buffer containing CLIgen command * @retval NULL Fatal error */ char * clicon_cliread(clicon_handle h) { - char *ret; - char *pfmt = NULL; + char *ret; + char *pfmt = NULL; cli_syntaxmode_t *mode; - cli_syntax_t *stx; - + cli_syntax_t *stx; + cli_prompthook_t *fn; + clixon_plugin *cp; + stx = cli_syntax(h); mode = stx->stx_active_mode; - - if (stx->stx_prompt_hook) - pfmt = stx->stx_prompt_hook(h, mode->csm_name); + /* Get prompt from plugin callback? */ + cp = NULL; + while ((cp = clixon_plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_prompt) == NULL) + continue; + pfmt = fn(h, mode->csm_name); + break; + } if (clicon_quiet_mode(h)) cli_prompt_set(h, ""); else @@ -745,42 +606,6 @@ clicon_cliread(clicon_handle h) return ret; } -/* - * cli_find_plugin - * Find a plugin by name and return the dlsym handl - * Used by libclicon code to find callback funcctions in plugins. - */ -static void * -cli_find_plugin(clicon_handle h, char *plugin) -{ - struct cli_plugin *p; - - p = plugin_find_cli(cli_syntax(h), plugin); - if (p) - return p->cp_handle; - - return NULL; -} - - -/*! Initialize plugin code (not the plugins themselves) - */ -int -cli_plugin_init(clicon_handle h) -{ - find_plugin_t *fp = cli_find_plugin; - clicon_hash_t *data = clicon_data(h); - - /* Register CLICON_FIND_PLUGIN in data hash */ - if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) { - clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN"); - return -1; - } - - return 0; -} - - /* * * CLI PLUGIN INTERFACE, PUBLIC SECTION @@ -788,11 +613,12 @@ cli_plugin_init(clicon_handle h) */ -/* - * Set syntax mode mode for existing current plugin group. +/*! Set syntax mode mode for existing current plugin group. + * @param[in] h Clicon handle */ int -cli_set_syntax_mode(clicon_handle h, const char *name) +cli_set_syntax_mode(clicon_handle h, + const char *name) { cli_syntaxmode_t *mode; @@ -803,8 +629,8 @@ cli_set_syntax_mode(clicon_handle h, const char *name) return 1; } -/* - * Get syntax mode name +/*! Get syntax mode name + * @param[in] h Clicon handle */ char * cli_syntax_mode(clicon_handle h) @@ -816,15 +642,15 @@ cli_syntax_mode(clicon_handle h) return csm->csm_name; } - -/* - * Callback from cli_set_prompt(). Set prompt format for syntax mode - * Arguments: - * name : Name of syntax mode - * prompt : Prompt format +/*! Callback from cli_set_prompt(). Set prompt format for syntax mode + * @param[in] h Clicon handle + * @param[in] name Name of syntax mode + * @param[in] prompt Prompt format */ int -cli_set_prompt(clicon_handle h, const char *name, const char *prompt) +cli_set_prompt(clicon_handle h, + const char *name, + const char *prompt) { cli_syntaxmode_t *m; @@ -836,9 +662,14 @@ cli_set_prompt(clicon_handle h, const char *name, const char *prompt) } /*! Format prompt + * @param[out] prompt Prompt string to be written + * @param[in] plen Length of prompt string + * @param[in] fmt Stdarg fmt string */ static int -prompt_fmt (char *prompt, size_t plen, char *fmt, ...) +prompt_fmt (char *prompt, + size_t plen, + char *fmt, ...) { va_list ap; char *s = fmt; @@ -854,7 +685,6 @@ prompt_fmt (char *prompt, size_t plen, char *fmt, ...) } /* Start with empty string */ - cprintf(cb, ""); while(*s) { if (*s == '%' && *++s) { switch(*s) { @@ -881,7 +711,6 @@ prompt_fmt (char *prompt, size_t plen, char *fmt, ...) cprintf(cb, "%c", *s); s++; } - done: if (cb) fmt = cbuf_get(cb); @@ -894,6 +723,7 @@ done: } /*! Return a formatted prompt string + * @param[in] fmt Format string */ char * cli_prompt(char *fmt) @@ -906,55 +736,3 @@ cli_prompt(char *fmt) return prompt; } -/*! Find a cli plugin based on name and resolve a function pointer in it. - * Callback from clicon_dbvars_parse() - * Find a cli plugin based on name if given and use dlsym to resolve a - * function pointer in it. - * Call the resolved function to get the cgv populated - */ -int -clicon_valcb(void *arg, cvec *vars, cg_var *cgv, char *fname, cg_var *funcarg) -{ - char *func; - char *plgnam = NULL; - void *handle; - struct cli_plugin *p; - cli_valcb_t *cb; - clicon_handle h = (clicon_handle)arg; - - /* Make copy */ - if ((fname = strdup(fname)) == NULL) { - clicon_err(OE_UNIX, errno, "strdup"); - return -1; - } - - /* Extract plugin name if any */ - if ((func = strstr(fname, "::")) != NULL) { - *func = '\0'; - func += 2; - plgnam = fname; - } - else - func = fname; - - /* If we have specified a plugin name, find the handle to be used - * with dlsym() - */ - handle = NULL; - if (plgnam && (p = plugin_find_cli(cli_syntax(h), plgnam))) - handle = p->cp_handle; - - /* Look up function pointer */ - if ((cb = dlsym(handle, func)) == NULL) { - clicon_err(OE_UNIX, errno, "unable to find %s()", func); - free(fname); - return -1; - } - free(fname); - - if (cb(vars, cgv, funcarg) < 0) - return -1; - - return 0; -} - diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index a81f057b..2df7fb47 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -43,54 +43,26 @@ /* clicon generic callback pointer */ typedef void (clicon_callback_t)(clicon_handle h); -/* clicon_set value callback */ -typedef int (cli_valcb_t)(cvec *vars, cg_var *cgv, cg_var *arg); - -/* specific to cli. For common see clicon_plugin.h */ -/* Hook to get prompt format before each getline */ -typedef char *(cli_prompthook_t)(clicon_handle, char *mode); - -/* Ctrl-Z hook from getline() */ -typedef int (cli_susphook_t)(clicon_handle, char *, int, int *); - -/* CLIgen parse failure hook. Retry other mode? */ -typedef char *(cli_parsehook_t)(clicon_handle, char *, char *); - +/* List of syntax modes */ typedef struct { - qelem_t csm_qelem; /* List header */ - char csm_name[256]; /* Syntax mode name */ - char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */ - int csm_nsyntax; /* Num syntax specs registered by plugin */ - parse_tree csm_pt; /* CLIgen parse tree */ + qelem_t csm_qelem; /* List header */ + char csm_name[256]; /* Syntax mode name */ + char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */ + int csm_nsyntax; /* Num syntax specs registered by plugin */ + parse_tree csm_pt; /* CLIgen parse tree */ } cli_syntaxmode_t; -/* A plugin list object */ -struct cli_plugin { - qelem_t cp_qelem; /* List header */ - char cp_name[256]; /* Plugin name */ - void *cp_handle; /* Dynamic object handle */ -}; - -/* Plugin group object */ +/* Plugin group object. Just a single object, not list. part of cli_handle */ typedef struct { - int stx_nplugins; /* Number of plugins */ - struct cli_plugin *stx_plugins; /* List of plugins */ int stx_nmodes; /* Number of syntax modes */ - cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */ - cli_syntaxmode_t *stx_modes; /* List of syntax modes */ - cli_prompthook_t *stx_prompt_hook; /* Prompt hook */ - cli_parsehook_t *stx_parse_hook; /* Parse mode hook */ - cli_susphook_t *stx_susp_hook; /* Ctrl-Z hook from getline() */ + cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */ + cli_syntaxmode_t *stx_modes; /* List of syntax modes */ } cli_syntax_t; void *clixon_str2fn(char *name, void *handle, char **error); -int cli_plugin_start(clicon_handle, int argc, char **argv); - -int cli_plugin_init(clicon_handle h); - int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr); int clicon_parse(clicon_handle h, char *cmd, char **mode, int *result); diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 0c3769d7..f4753f3c 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -94,8 +94,8 @@ expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, - cvec *commands, - cvec *helptexts) + cvec *commands, + cvec *helptexts) { int retval = -1; char *api_path_fmt; @@ -120,7 +120,8 @@ expand_dbvar(void *h, yang_stmt *ypath; cxobj *xcur; char *xpathcur; - + char *reason = NULL; + if (argv == NULL || cvec_len(argv) != 2){ clicon_err(OE_PLUGIN, 0, "%s: requires arguments: ", __FUNCTION__); @@ -171,8 +172,10 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; + if (y==NULL) + goto ok; /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole * tree and apply the path to that. @@ -188,8 +191,12 @@ expand_dbvar(void *h, goto done; } xpathcur = ypath->ys_argument; - if (xml_merge(xt, xtop, yspec) < 0) /* Merge xtop into xt */ + if (xml_merge(xt, xtop, yspec, &reason) < 0) /* Merge xtop into xt */ goto done; + if (reason){ + cli_output(stderr, "%s\n", reason); + goto done; + } if ((xcur = xpath_first(xt, xpath)) == NULL){ clicon_err(OE_DB, 0, "xpath %s should return merged content", xpath); goto done; @@ -236,8 +243,11 @@ expand_dbvar(void *h, /* XXX RFC3986 decode */ cvec_add_string(commands, NULL, bodystr); } + ok: retval = 0; done: + if (reason) + free(reason); if (api_path) free(api_path); if (xvec) @@ -506,12 +516,12 @@ cli_show_config(clicon_handle h, xml2txt(stdout, xc, 0); /* tree-formed text */ break; case FORMAT_CLI: + /* get CLI generatade mode: VARS|ALL */ + if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) + goto done; xc = NULL; /* Dont print xt itself */ - while ((xc = xml_child_each(xt, xc, -1)) != NULL){ - if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) - goto done; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) xml2cli(stdout, xc, NULL, gt); /* cli syntax */ - } break; case FORMAT_NETCONF: fprintf(stdout, "\n"); @@ -587,7 +597,14 @@ done: xml_free(xt); return retval; } + int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv) { return show_conf_xpath(h, vars, argv); } + +int cli_show_version(clicon_handle h, cvec *vars, cvec *argv) +{ + cli_output(stdout, "%s\n", CLIXON_VERSION_STRING); + return 0; +} diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 523a51a2..81d86577 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -35,6 +35,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -44,6 +45,7 @@ bindir = @bindir@ libdir = @libdir@ mandir = @mandir@ libexecdir = @libexecdir@ +wwwdir = /www-data localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ includedir = @includedir@ @@ -64,22 +66,32 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LI CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -APPL = clixon_netconf -SRC = netconf_main.c -OBJS = $(SRC:.c=.o) +# Name of application +APPL = clixon_netconf +# Not accessible from plugin +APPSRC = netconf_main.c +APPSRC += netconf_hello.c +APPSRC += netconf_rpc.c +APPSRC += netconf_filter.c +APPOBJ = $(APPSRC:.c=.o) + +# Accessible from plugin +LIBSRC = netconf_lib.c + + +LIBOBJ = $(LIBSRC:.c=.o) + +# Name of lib MYNAME = clixon_netconf MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) -LIBSRC = netconf_hello.c netconf_rpc.c netconf_filter.c netconf_lib.c netconf_plugin.c -LIBOBJS = $(LIBSRC:.c=.o) - all: $(MYLIB) $(APPL) clean: - rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) + rm -f $(APPL) $(APPOBJ) $(LIBOBJ) *.core $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend @@ -89,18 +101,18 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_netconf.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_netconf.so install-include: clixon_netconf.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(bindir)/$(APPL) @@ -113,14 +125,14 @@ uninstall: .c.o: $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< -$(APPL) : $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ +$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(MYLIB) : $(LIBOBJS) +$(MYLIB) : $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) - $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) else - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) endif # link-name is needed for application linking, eg for clixon_cli and clixon_config @@ -132,7 +144,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index e3b1680e..e2fbb5bb 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -39,27 +39,12 @@ #ifndef _CLIXON_NETCONF_H_ #define _CLIXON_NETCONF_H_ -/* - * Types - */ -typedef int (*netconf_cb_t)( - clicon_handle h, - cxobj *xn, /* Request: */ - cxobj **xret, /* Return xml tree, eg ... */ - void *arg /* Argument given at netconf_register_callback() */ - ); - /* * Prototypes * (Duplicated. Also in netconf_*.h) */ int netconf_output(int s, cbuf *xf, char *msg); -int netconf_register_callback(clicon_handle h, - netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag); /* Xml tag when callback is made */ - int netconf_xpath(cxobj *xsearch, cxobj *xfilter, cbuf *xf, cbuf *xf_err, diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 68b42805..833a1c5c 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -122,7 +122,7 @@ netconf_create_hello(cbuf *xf, /* msg buffer */ cprintf(xf, "urn:ietf:params:netconf:capability:notification:1.0\n"); cprintf(xf, "urn:ietf:params:netconf:capability:startup:1.0\n"); cprintf(xf, ""); - cprintf(xf, "%lu", 42+session_id); + cprintf(xf, "%lu", (long unsigned int)42+session_id); cprintf(xf, ""); add_postamble(xf); return retval; diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index debd0c3d..5e5e21d2 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -71,45 +71,48 @@ enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */ int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */ +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. + * @param[in] cb Netconf packet (cligen buffer) + */ int -add_preamble(cbuf *xf) +add_preamble(cbuf *cb) { if (transport == NETCONF_SOAP) - cprintf(xf, "\n\n" + cprintf(cb, "\n\n" ""); return 0; } -/* - * add_postamble - * add netconf xml postamble of message. That is, xml after the body of the message. +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. * for soap this is the envelope stuff, for ssh this is ]]>]]> + * @param[in] cb Netconf packet (cligen buffer) */ int -add_postamble(cbuf *xf) +add_postamble(cbuf *cb) { switch (transport){ case NETCONF_SSH: - cprintf(xf, "]]>]]>"); /* Add RFC4742 end-of-message marker */ + cprintf(cb, "]]>]]>"); /* Add RFC4742 end-of-message marker */ break; case NETCONF_SOAP: - cprintf(xf, "\n" ""); + cprintf(cb, "\n" ""); break; } return 0; } -/* - * add_error_preamble +/*! Add error_preamble * compared to regular messages (see add_preamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_preamble(cbuf *xf, char *reason) +add_error_preamble(cbuf *cb, + char *reason) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" + cprintf(cb, "" "" "" "" @@ -121,26 +124,26 @@ add_error_preamble(cbuf *xf, char *reason) "", reason); break; default: - if (add_preamble(xf) < 0) + if (add_preamble(cb) < 0) return -1; break; } return 0; } -/* - * add_error_postamble +/*! Add error postamble * compared to regular messages (see add_postamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_postamble(cbuf *xf) +add_error_postamble(cbuf *cb) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" ""); + cprintf(cb, "" ""); default: /* fall through */ - if (add_postamble(xf) < 0) + if (add_postamble(cb) < 0) return -1; break; } @@ -150,7 +153,9 @@ add_error_postamble(cbuf *xf) /*! Get "target" attribute, return actual database given candidate or running * Caller must do error handling - * @retval dbname Actual database file name + * @param[in] xn XML tree + * @param[in] path + * @retval dbname Actual database file name */ char * netconf_get_target(cxobj *xn, @@ -180,11 +185,11 @@ netconf_get_target(cxobj *xn, */ int netconf_output(int s, - cbuf *xf, + cbuf *cb, char *msg) { - char *buf = cbuf_get(xf); - int len = cbuf_len(xf); + char *buf = cbuf_get(cb); + int len = cbuf_len(cb); int retval = -1; clicon_debug(1, "SEND %s", msg); @@ -207,3 +212,4 @@ netconf_output(int s, done: return retval; } + diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index c70ae1c6..20fd8cdf 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,6 @@ #include "clixon_netconf.h" #include "netconf_lib.h" #include "netconf_hello.h" -#include "netconf_plugin.h" #include "netconf_rpc.h" /* Command line options to be passed to getopt(3) */ @@ -100,13 +100,9 @@ process_incoming_packet(clicon_handle h, /* Parse incoming XML message */ if (xml_parse_string(str, NULL, &xreq) < 0){ if ((cbret = cbuf_new()) == NULL){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "internal error" - ""); - netconf_output(1, cb, "rpc-error"); + if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); } else clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__); @@ -205,7 +201,6 @@ netconf_input_cb(int s, retval = 0; goto done; } - for (i=0; ipw_name) < 0) + goto done; + while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1) switch (c) { case 'h' : /* help */ @@ -379,6 +386,8 @@ main(int argc, argc -= optind; argv += optind; + + /* Parse yang database spec file */ if (yang_spec_main(h) == NULL) goto done; @@ -388,13 +397,14 @@ main(int argc, goto done; /* Initialize plugins group */ - if (netconf_plugin_load(h) < 0) - goto done; + if ((dir = clicon_netconf_dir(h)) != NULL) + if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) + goto done; /* Call start function is all plugins before we go interactive */ tmp = *(argv-1); *(argv-1) = argv0; - netconf_plugin_start(h, argc+1, argv-1); + clixon_plugin_start(h, argc+1, argv-1); *(argv-1) = tmp; if (!quiet) @@ -406,7 +416,6 @@ main(int argc, if (event_loop() < 0) goto done; done: - netconf_plugin_unload(h); netconf_terminate(h); clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c deleted file mode 100644 index b601969f..00000000 --- a/apps/netconf/netconf_plugin.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - This file is part of CLIXON. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - Alternatively, the contents of this file may be used under the terms of - the GNU General Public License Version 3 or later (the "GPL"), - in which case the provisions of the GPL are applicable instead - of those above. If you wish to allow use of your version of this file only - under the terms of the GPL, and not to allow others to - use your version of this file under the terms of Apache License version 2, - indicate your decision by deleting the provisions above and replace them with - the notice and other provisions required by the GPL. If you do not delete - the provisions above, a recipient may use your version of this file under - the terms of any one of the Apache License version 2 or the GPL. - - ***** END LICENSE BLOCK ***** - - * - * handling netconf plugins - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -/* clicon netconf*/ -#include "clixon_netconf.h" -#include "netconf_lib.h" -#include "netconf_plugin.h" - -/* Database dependency description */ -struct netconf_reg { - qelem_t nr_qelem; /* List header */ - netconf_cb_t nr_callback; /* Validation/Commit Callback */ - void *nr_arg; /* Application specific argument to cb */ - char *nr_tag; /* Xml tag when matched, callback called */ -}; -typedef struct netconf_reg netconf_reg_t; - -static int nplugins = 0; -static plghndl_t *plugins = NULL; -static netconf_reg_t *deps = NULL; - -/*! Load all plugins you can find in CLICON_NETCONF_DIR - */ -int -netconf_plugin_load(clicon_handle h) -{ - int retval = -1; - char *dir; - int ndp; - struct dirent *dp = NULL; - int i; - char filename[MAXPATHLEN]; - plghndl_t *handle; - - /* If no DIR defined, then dont load plugins */ - if ((dir = clicon_netconf_dir(h)) == NULL){ - retval = 0; - goto quit; - } - - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* Load all plugins */ - for (i = 0; i < ndp; i++) { - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", - (int)strlen(filename), filename); - if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) - goto quit; - if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - goto quit; - } - plugins[nplugins++] = handle; - } - retval = 0; -quit: - if (dp) - free(dp); - return retval; -} - -/*! Unload all netconf plugins */ -int -netconf_plugin_unload(clicon_handle h) -{ - int i; - netconf_reg_t *nr; - - while((nr = deps) != NULL) { - DELQ(nr, deps, netconf_reg_t *); - if (nr->nr_tag) - free(nr->nr_tag); - free(nr); - } - for (i = 0; i < nplugins; i++) - plugin_unload(h, plugins[i]); - if (plugins){ - free(plugins); - plugins = NULL; - } - nplugins = 0; - return 0; -} - -/*! Call plugin_start in all plugins - */ -int -netconf_plugin_start(clicon_handle h, int argc, char **argv) -{ - int i; - plgstart_t *startfn; - - for (i = 0; i < nplugins; i++) { - /* Call exit function is it exists */ - if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL) - break; - optind = 0; - if (startfn(h, argc, argv) < 0) { - clicon_debug(1, "plugin_start() failed\n"); - return -1; - } - } - return 0; -} - - -/*! Register netconf callback - * Called from plugin to register a callback for a specific netconf XML tag. - */ -int -netconf_register_callback(clicon_handle h, - netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag) /* Xml tag when callback is made */ -{ - netconf_reg_t *nr; - - if ((nr = malloc(sizeof(netconf_reg_t))) == NULL) { - clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); - goto catch; - } - memset (nr, 0, sizeof (*nr)); - nr->nr_callback = cb; - nr->nr_arg = arg; - nr->nr_tag = strdup(tag); /* strdup */ - INSQ(nr, deps); - return 0; -catch: - if (nr){ - if (nr->nr_tag) - free(nr->nr_tag); - free(nr); - } - return -1; -} - -/*! See if there is any callback registered for this tag - * - * Look for local (client-side) netconf plugins. This feature may no - * longer be necessary as generic RPC:s should be handled by backend. - * - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at child of rpc: . - * @param[out] xret Return XML, error or OK - * - * @retval -1 Error - * @retval 0 OK, not found handler. - * @retval 1 OK, handler called - */ -int -netconf_plugin_callbacks(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - netconf_reg_t *nreg; - - if (deps != NULL){ - nreg = deps; - do { - if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){ - if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0) - goto done; - retval = 1; /* handled */ - goto done; - } - nreg = NEXTQ(netconf_reg_t *, nreg); - } while (nreg != deps); - } - retval = 0; - done: - return retval; -} - diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index a87a8bfb..83484335 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -65,7 +65,6 @@ #include "clixon_netconf.h" #include "netconf_lib.h" #include "netconf_filter.h" -#include "netconf_plugin.h" #include "netconf_rpc.h" /* @@ -850,8 +849,10 @@ netconf_create_subscription(clicon_handle h, return retval; } -/*! See if there is any callback registered for this tag +/*! See if there is any application defined RPC for this tag * + * This may either be local client-side or backend. If backend send as netconf + * RPC. * @param[in] h clicon handle * @param[in] xn Sub-tree (under xorig) at child of rpc: . * @param[out] xret Return XML, error or OK @@ -872,6 +873,8 @@ netconf_application_rpc(clicon_handle h, yang_stmt *youtput; cxobj *xoutput; cbuf *cb = NULL; + cbuf *cbret = NULL; + int ret; /* First check system / netconf RPC:s */ if ((cb = cbuf_new()) == NULL){ @@ -890,11 +893,11 @@ netconf_application_rpc(clicon_handle h, // else // cprintf(cb, "/%s", xml_name(xn)); /* XXX not accepdted by below */ /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) + if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0) goto done; - /* Check if found */ if (yrpc != NULL){ + /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -905,13 +908,20 @@ netconf_application_rpc(clicon_handle h, if (xml_yang_validate_add(xn, NULL) < 0) goto done; } - /* - * 1. Check xn arguments with input statement. - * 2. Send to backend as clicon_msg-encode() - * 3. In backend to similar but there call actual backend - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; + } + /* Look for local (client-side) netconf plugins. */ + if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + goto done; + } + else /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; /* Sanity check of outgoing XML */ if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); @@ -931,9 +941,10 @@ netconf_application_rpc(clicon_handle h, done: if (cb) cbuf_free(cb); + if (cbret) + cbuf_free(cbret); return retval; } - /*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions. @@ -1018,14 +1029,10 @@ netconf_rpc_dispatch(clicon_handle h, } /* Others */ else { - /* Look for local (client-side) netconf plugins. This feature may no - * longer be necessary as generic RPC:s should be handled by backend. - */ - if ((retval = netconf_plugin_callbacks(h, xe, xret)) < 0) + /* Look for application-defined RPC. This may either be local + client-side or backend. If backend send as netconf RPC. */ + if ((retval = netconf_application_rpc(h, xe, xret)) < 0) goto done; - if (retval == 0) - if ((retval = netconf_application_rpc(h, xe, xret)) < 0) - goto done; if (retval == 0){ /* not handled by callback */ xml_parse_va(xret, NULL, "" "operation-failed" diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 990529cc..d54f1255 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -35,15 +35,21 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ +datarootdir = @datarootdir@ exec_prefix = @exec_prefix@ bindir = @bindir@ -wwwdir = /www-data +libdir = @libdir@ +mandir = @mandir@ libexecdir = @libexecdir@ +wwwdir = /www-data localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ +includedir = @includedir@ +HOST_VENDOR = @host_vendor@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -57,23 +63,32 @@ LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) -CPPFLAGS = @CPPFLAGS@ +CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -SRC = restconf_lib.c -SRC += restconf_methods.c +# Name of application +APPL = clixon_restconf -OBJS = $(SRC:.c=.o) +# Not accessible from plugin +APPSRC = restconf_main.c +APPSRC += restconf_methods.c +APPOBJ = $(APPSRC:.c=.o) -APPSRC = restconf_main.c -APPOBJ = $(APPSRC:.c=.o) -APPL = clixon_restconf +# Accessible from plugin +LIBSRC = restconf_lib.c +LIBOBJ = $(LIBSRC:.c=.o) -all: $(APPL) +# Name of lib +MYNAME = clixon_restconf +MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) +MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) +MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) + +all: $(MYLIB) $(APPL) clean: - rm -f $(OBJS) *.core $(APPL) $(APPOBJ) + rm -f $(LIBOBJ) *.core $(APPL) $(APPOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend @@ -82,14 +97,23 @@ distclean: clean # Put other executables in libexec/ # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ -install: $(APPL) - install -d $(DESTDIR)$(wwwdir) - install $(APPL) $(DESTDIR)$(wwwdir) +install: install-lib $(APPL) + install -d -m 0755 $(DESTDIR)$(wwwdir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(wwwdir) -install-include: +install-lib: $(MYLIB) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) + ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_restconf.so.2 + ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_restconf.so + +install-include: clixon_restconf.h + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(wwwdir)/$(APPL) + rm -f $(DESTDIR)$(libdir)/$(MYLIBLINK)* .SUFFIXES: .SUFFIXES: .c .o @@ -97,14 +121,24 @@ uninstall: .c.o: $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< -$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS) - $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@ +$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ + +$(MYLIB) : $(LIBOBJ) +ifeq ($(HOST_VENDOR),apple) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) +else + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) +endif + +# link-name is needed for application linking, eg for clixon_cli and clixon_config +$(MYLIBLINK) : $(MYLIB) TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/restconf/README.md b/apps/restconf/README.md index fdb1da37..d6c69b89 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -1,16 +1,4 @@ # Clixon Restconf -### Features - -Clixon restconf is a daemon based on FASTCGI. Instructions are available to -run with NGINX. -The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040). -The following featires are supported: -- OPTIONS, HEAD, GET, POST, PUT, DELETE -The following are not implemented -- PATCH -- query parameters (section 4.9) -- notifications (sec 6) -- schema resource ### Installation using Nginx @@ -32,7 +20,7 @@ sudo /etc/init.d nginx start Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/routing.xml " -s /bin/sh www-data +olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data ``` Make restconf calls with curl @@ -68,7 +56,7 @@ curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enable Start the restconf fastcgi program with debug flag: ``` -sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml" -s /bin/sh www-data +sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml" -s /bin/sh www-data ``` Look at syslog: ``` diff --git a/apps/netconf/netconf_plugin.h b/apps/restconf/clixon_restconf.h similarity index 61% rename from apps/netconf/netconf_plugin.h rename to apps/restconf/clixon_restconf.h index 96cf2921..2c79c3cc 100644 --- a/apps/netconf/netconf_plugin.h +++ b/apps/restconf/clixon_restconf.h @@ -31,26 +31,33 @@ ***** END LICENSE BLOCK ***** - * - * handling netconf plugins - *****************************************************************************/ -#ifndef _NETCONF_PLUGIN_H_ -#define _NETCONF_PLUGIN_H_ - -/* - * Types + * The exported interface to plugins. External apps (eg frontend restconf plugins) + * should only include this file (not the restconf_*.h) */ +#ifndef _CLIXON_RESTCONF_H_ +#define _CLIXON_RESTCONF_H_ /* - * Prototypes - */ -int netconf_plugin_load(clicon_handle h); + * Prototypes (also in restconf_lib.h) + */ +int restconf_err2code(char *tag); +const char *restconf_code2reason(int code); -int netconf_plugin_start(clicon_handle h, int argc, char **argv); +int badrequest(FCGX_Request *r); +int unauthorized(FCGX_Request *r); +int forbidden(FCGX_Request *r); +int notfound(FCGX_Request *r); +int conflict(FCGX_Request *r); +int internal_server_error(FCGX_Request *r); +int notimplemented(FCGX_Request *r); -int netconf_plugin_unload(clicon_handle h); +int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); +int test(FCGX_Request *r, int dbg); +cbuf *readdata(FCGX_Request *r); +int get_user_cookie(char *cookiestr, char *attribute, char **val); +int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, + int pretty, int use_xml); -int netconf_plugin_callbacks(clicon_handle h, cxobj *xn, cxobj **xret); -#endif /* _NETCONF_PLUGIN_H_ */ +#endif /* _CLIXON_RESTCONF_H_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 15c868ca..ab5db7b5 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -55,6 +54,8 @@ /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + #include "restconf_lib.h" /* See RFC 8040 Section 7: Mapping from NETCONF to Status Code @@ -211,8 +212,10 @@ notfound(FCGX_Request *r) clicon_debug(1, "%s", __FUNCTION__); path = FCGX_GetParam("DOCUMENT_URI", r->envp); FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */ + FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(r->out, "

Not Found

\n"); + FCGX_FPrintF(r->out, "Not Found\n"); FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n", path); return 0; @@ -326,6 +329,7 @@ test(FCGX_Request *r, printparam(r, "HTTPS", dbg); printparam(r, "HTTP_ACCEPT", dbg); printparam(r, "HTTP_CONTENT_TYPE", dbg); + printparam(r, "HTTP_AUTHORIZATION", dbg); #if 0 /* For debug */ clicon_debug(1, "All environment vars:"); { @@ -356,129 +360,6 @@ readdata(FCGX_Request *r) return cb; } - -static int nplugins = 0; -static plghndl_t *plugins = NULL; -static plgcredentials_t *_credentials_fn = NULL; /* Credentials callback */ - -/*! Load all plugins you can find in CLICON_RESTCONF_DIR - */ -int -restconf_plugin_load(clicon_handle h) -{ - int retval = -1; - char *dir; - int ndp; - struct dirent *dp = NULL; - int i; - plghndl_t *handle; - char filename[MAXPATHLEN]; - - if ((dir = clicon_restconf_dir(h)) == NULL){ - retval = 0; - goto quit; - } - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* Load all plugins */ - for (i = 0; i < ndp; i++) { - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", - (int)strlen(filename), filename); - if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) - goto quit; - if ((_credentials_fn = dlsym(handle, PLUGIN_CREDENTIALS)) == NULL) - clicon_debug(1, "Failed to load %s", PLUGIN_CREDENTIALS); - else - clicon_debug(1, "%s callback loaded", PLUGIN_CREDENTIALS); - if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - goto quit; - } - plugins[nplugins++] = handle; - } - retval = 0; -quit: - if (dp) - free(dp); - return retval; -} - - -/*! Unload all restconf plugins */ -int -restconf_plugin_unload(clicon_handle h) -{ - int i; - - for (i = 0; i < nplugins; i++) - plugin_unload(h, plugins[i]); - if (plugins){ - free(plugins); - plugins = NULL; - } - nplugins = 0; - return 0; -} - -/*! Call plugin_start in all plugins - */ -int -restconf_plugin_start(clicon_handle h, - int argc, - char **argv) -{ - int i; - plgstart_t *startfn; - - for (i = 0; i < nplugins; i++) { - /* Call exit function is it exists */ - if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL) - break; - optind = 0; - if (startfn(h, argc, argv) < 0) { - clicon_debug(1, "plugin_start() failed\n"); - return -1; - } - } - return 0; -} - -/*! Run the restconf user-defined credentials callback if present - * The callback is expected to return the authenticated user, or NULL if not - * authenticasted. - * If no callback exists, return user "none" - * @param[in] h Clicon handle - * @param[in] r Fastcgi request handle - * @param[out] user The authenticated user (or NULL). Malloced, must be freed. - */ -int -restconf_credentials(clicon_handle h, - FCGX_Request *r, - char **user) -{ - int retval = -1; - - clicon_debug(1, "%s", __FUNCTION__); - /* If no authentication callback then allow anything. Is this OK? */ - if (_credentials_fn == NULL){ - if ((*user = strdup("none")) == NULL){ - clicon_err(OE_XML, errno, "strdup"); - goto done; - } - goto ok; - } - if (_credentials_fn(h, r, user) < 0) - *user = NULL; - ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d user:%s", __FUNCTION__, retval, *user); - return retval; -} - /*! Parse a cookie string and return value of cookie attribute * @param[in] cookiestr cookie string according to rfc6265 (modified) * @param[in] attribute cookie attribute @@ -505,3 +386,82 @@ get_user_cookie(char *cookiestr, cvec_free(cvv); return retval; } + +/*! Return restconf error on get/head request + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] xerr XML error message from backend + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + */ +int +api_return_err(clicon_handle h, + FCGX_Request *r, + cxobj *xerr, + int pretty, + int use_xml) +{ + int retval = -1; + cbuf *cb = NULL; + cxobj *xtag; + char *tagstr; + int code; + const char *reason_phrase; + + clicon_debug(1, "%s", __FUNCTION__); + if ((cb = cbuf_new()) == NULL) + goto done; + if ((xtag = xpath_first(xerr, "//error-tag")) == NULL){ + notfound(r); + goto ok; + } + tagstr = xml_body(xtag); + code = restconf_err2code(tagstr); + if ((reason_phrase = restconf_code2reason(code)) == NULL) + reason_phrase=""; + if (xml_name_set(xerr, "error") < 0) + goto done; + if (use_xml){ + if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0) + goto done; + } + else + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; + 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", + use_xml?"xml":"json"); + if (use_xml){ + if (pretty){ + FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " \r\n"); + } + else { + FCGX_FPrintF(r->out, "", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "\r\n"); + } + } + else{ + if (pretty){ + FCGX_FPrintF(r->out, "{\n"); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", + cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } + else{ + FCGX_FPrintF(r->out, "{"); + FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } + } + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); + return retval; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 59e381f4..cf8ef66b 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -37,11 +37,7 @@ #define _RESTCONF_LIB_H_ /* - * Constants - */ - -/* - * Prototypes + * Prototypes (also in clixon_restconf.h) */ int restconf_err2code(char *tag); const char *restconf_code2reason(int code); @@ -57,12 +53,8 @@ int notimplemented(FCGX_Request *r); int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); - -int restconf_plugin_load(clicon_handle h); -int restconf_plugin_start(clicon_handle h, int argc, char **argv); -int restconf_plugin_unload(clicon_handle h); -int restconf_credentials(clicon_handle h, FCGX_Request *r, char **user); int get_user_cookie(char *cookiestr, char *attribute, char **val); - +int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, + int pretty, int use_xml); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 54b422ed..7ffc9260 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= @@ -54,7 +54,8 @@ #include #include #include -#include +#include + #include #include #include @@ -66,6 +67,8 @@ /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + /* restconf */ #include "restconf_lib.h" #include "restconf_methods.h" @@ -89,7 +92,10 @@ * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] dvec Stream input data + * @param[in] dvec Stream input daat + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data */ static int api_data(clicon_handle h, @@ -98,7 +104,10 @@ api_data(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; char *request_method; @@ -109,17 +118,17 @@ api_data(clicon_handle h, if (strcmp(request_method, "OPTIONS")==0) retval = api_data_options(h, r); else if (strcmp(request_method, "HEAD")==0) - retval = api_data_head(h, r, pcvec, pi, qvec); + retval = api_data_head(h, r, pcvec, pi, qvec, pretty, use_xml); else if (strcmp(request_method, "GET")==0) - retval = api_data_get(h, r, pcvec, pi, qvec); + retval = api_data_get(h, r, pcvec, pi, qvec, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data); + retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PUT")==0) - retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data); + retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PATCH")==0) retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data); else if (strcmp(request_method, "DELETE")==0) - retval = api_data_delete(h, r, api_path, pi); + retval = api_data_delete(h, r, api_path, pi, pretty, use_xml); else retval = notfound(r); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -129,11 +138,12 @@ api_data(clicon_handle h, /*! Operations REST method, POST * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle - * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] dvec Stream input data + * @param[in] data Stream input data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data */ static int api_operations(clicon_handle h, @@ -143,7 +153,9 @@ api_operations(clicon_handle h, int pi, cvec *qvec, char *data, - char *username) + int pretty, + int use_xml, + int parse_xml) { int retval = -1; char *request_method; @@ -152,16 +164,43 @@ api_operations(clicon_handle h, request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); if (strcmp(request_method, "GET")==0) - retval = api_operation_get(h, r, path, pcvec, pi, qvec, data); + retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, + pretty, use_xml, parse_xml); else retval = notfound(r); return retval; } +/*! Determine the root of the RESTCONF API + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle + * @note Hardcoded to "/restconf" + * Return see RFC8040 3.1 and RFC7320 + * In line with the best practices defined by [RFC7320], RESTCONF + * enables deployments to specify where the RESTCONF API is located. + */ +static int +api_well_known(clicon_handle h, + FCGX_Request *r) +{ + clicon_debug(1, "%s", __FUNCTION__); + FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "\n"); + FCGX_FPrintF(r->out, " \n"); + FCGX_FPrintF(r->out, "\r\n"); + + return 0; +} + /*! Retrieve the Top-Level API Resource + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle * @note Only returns null for operations and data,... + * See RFC8040 3.3 */ static int api_root(clicon_handle h, @@ -179,9 +218,11 @@ api_root(clicon_handle h, media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); if (strcmp(media_accept, "application/yang-data+xml")==0) use_xml++; + clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); + if (xml_parse_string("2016-06-21", NULL, &xt) < 0) goto done; if ((cb = cbuf_new()) == NULL){ @@ -235,7 +276,6 @@ api_yang_library_version(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (use_xml){ @@ -247,8 +287,8 @@ api_yang_library_version(clicon_handle h, goto done; } clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb)); - FCGX_FPrintF(r->out, "%s\r\n", cb?cbuf_get(cb):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):""); + FCGX_FPrintF(r->out, "\n\n"); retval = 0; done: if (cb) @@ -263,7 +303,7 @@ api_yang_library_version(clicon_handle h, */ static int api_restconf(clicon_handle h, - FCGX_Request *r) + FCGX_Request *r) { int retval = -1; char *path; @@ -276,17 +316,34 @@ api_restconf(clicon_handle h, cvec *pcvec = NULL; /* for rest api */ cbuf *cb = NULL; char *data; - char *username = NULL; + int authenticated = 0; + char *media_accept; + char *media_content_type; + int pretty; + int parse_xml = 0; /* By default expect and parse JSON */ + int use_xml = 0; /* By default use JSON */ + cbuf *cbret = NULL; + cxobj *xret = NULL; + cxobj *xerr; clicon_debug(1, "%s", __FUNCTION__); path = FCGX_GetParam("REQUEST_URI", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + /* get xml/json in put and output */ + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (media_content_type && + strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) goto done; /* Sanity check of path. Should be /restconf/ */ if (pn < 2){ - retval = notfound(r); - goto done; + notfound(r); + goto ok; } if (strlen(pvec[0]) != 0){ retval = notfound(r); @@ -296,6 +353,8 @@ api_restconf(clicon_handle h, retval = notfound(r); goto done; } + test(r, 1); + if (pn == 2){ retval = api_root(h, r); goto done; @@ -304,7 +363,7 @@ api_restconf(clicon_handle h, retval = notfound(r); goto done; } - clicon_debug(1, "method=%s", method); + clicon_debug(1, "%s: method=%s", __FUNCTION__, method); if (str2cvec(query, '&', '=', &qvec) < 0) goto done; if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ @@ -313,33 +372,45 @@ api_restconf(clicon_handle h, if ((cb = readdata(r)) == NULL) goto done; data = cbuf_get(cb); - clicon_debug(1, "DATA=%s", data); + clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); + if (str2cvec(data, '&', '=', &dvec) < 0) goto done; - - test(r, 1); /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ - if (restconf_credentials(h, r, &username) < 0) + if ((authenticated = clixon_plugin_auth(h, r)) < 0) goto done; - clicon_debug(1, "%s username:%s", __FUNCTION__, username); - clicon_debug(1, "%s credentials ok username:%s (should be non-NULL)", - __FUNCTION__, username); - if (username == NULL){ - unauthorized(r); + clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); + + /* If set but no user, we set a dummy user */ + if (authenticated){ + if (clicon_username_get(h) == NULL) + clicon_username_set(h, "none"); + } + else{ + if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } goto ok; } + clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); if (strcmp(method, "yang-library-version")==0){ if (api_yang_library_version(h, r) < 0) goto done; } else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, r, path, pcvec, 2, qvec, data) < 0) + if (api_data(h, r, path, pcvec, 2, qvec, data, + pretty, use_xml, parse_xml) < 0) goto done; } else if (strcmp(method, "operations") == 0){ /* rpc */ - if (api_operations(h, r, path, pcvec, 2, qvec, data, username) < 0) + if (api_operations(h, r, path, pcvec, 2, qvec, data, + pretty, use_xml, parse_xml) < 0) goto done; } else if (strcmp(method, "test") == 0) @@ -360,35 +431,20 @@ api_restconf(clicon_handle h, cvec_free(pcvec); if (cb) cbuf_free(cb); - if (username) - free(username); + if (cbret) + cbuf_free(cbret); + if (xret) + xml_free(xret); return retval; } -/*! Process a FastCGI request - * @param[in] r Fastcgi request handle - */ -static int -api_well_known(clicon_handle h, - FCGX_Request *r) -{ - clicon_debug(1, "%s", __FUNCTION__); - FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, " \r\n"); - FCGX_FPrintF(r->out, "\r\n"); - - return 0; -} - static int restconf_terminate(clicon_handle h) { yang_spec *yspec; - clicon_debug(0, "%s", __FUNCTION__); + clixon_plugin_exit(h); + rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -447,6 +503,7 @@ main(int argc, { int retval = -1; int sock; + char *argv0 = argv[0]; FCGX_Request request; FCGX_Request *r = &request; char c; @@ -454,7 +511,9 @@ main(int argc, char *path; clicon_handle h; char *yangspec=NULL; - + char *dir; + char *tmp; + /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG); /* Create handle */ @@ -510,13 +569,22 @@ main(int argc, clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", yangspec); /* Initialize plugins group */ - if (restconf_plugin_load(h) < 0) - return -1; + if ((dir = clicon_restconf_dir(h)) != NULL) + if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) + return -1; /* Parse yang database spec file */ if (yang_spec_main(h) == NULL) goto done; + /* Call start function in all plugins before we go interactive + Pass all args after the standard options to plugin_start + */ + tmp = *(argv-1); + *(argv-1) = argv0; + clixon_plugin_start(h, argc+1, argv-1); + *(argv-1) = tmp; + if ((sockpath = clicon_option_str(h, "CLICON_RESTCONF_PATH")) == NULL){ clicon_err(OE_CFG, errno, "No CLICON_RESTCONF_PATH in clixon configure file"); goto done; @@ -545,21 +613,19 @@ main(int argc, if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0) api_restconf(h, r); /* This is the function */ else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { - api_well_known(h, r); /* This is the function */ + api_well_known(h, r); /* */ } else{ clicon_debug(1, "top-level %s not found", path); notfound(r); } - } else clicon_debug(1, "NULL URI"); - FCGX_Finish_r(r); + FCGX_Finish_r(r); } retval = 0; done: - restconf_plugin_unload(h); restconf_terminate(h); return retval; } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 69bae763..b65c3c27 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= @@ -104,8 +104,8 @@ Mapping netconf error-tag -> status code #include #include #include -#include #include +#include #include #include @@ -115,6 +115,8 @@ Mapping netconf error-tag -> status code /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + #include "restconf_lib.h" #include "restconf_methods.h" @@ -140,53 +142,6 @@ api_data_options(clicon_handle h, return 0; } -/*! Return error on get/head request - * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle - * @param[in] xerr XML error message from backend - */ -static int -api_data_get_err(clicon_handle h, - FCGX_Request *r, - cxobj *xerr) -{ - int retval = -1; - cbuf *cbj = NULL; - cxobj *xtag; - int code; - const char *reason_phrase; - - if ((cbj = cbuf_new()) == NULL) - goto done; - if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ - notfound(r); /* bad reply? */ - goto ok; - } - code = restconf_err2code(xml_body(xtag)); - if ((reason_phrase = restconf_code2reason(code)) == NULL) - reason_phrase=""; - clicon_debug(1, "%s code:%d reason phrase:%s", - __FUNCTION__, code, reason_phrase); - - if (xml_name_set(xerr, "error") < 0) - goto done; - if (xml2json_cbuf(cbj, xerr, 1) < 0) - goto done; - FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, "{\r\n"); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n"); - FCGX_FPrintF(r->out, " %s", cbuf_get(cbj)); - FCGX_FPrintF(r->out, " }\r\n"); - FCGX_FPrintF(r->out, "}\r\n"); - ok: - retval = 0; - done: - if (cbj) - cbuf_free(cbj); - return retval; -} /*! Generic GET (both HEAD and GET) * According to restconf @@ -195,10 +150,13 @@ api_data_get_err(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * @param[in] head If 1 is HEAD, otherwise GET * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode + * See RFC8040 Sec 4.2 and 4.3 * XXX: cant find a way to use Accept request field to choose Content-Type * I would like to support both xml and json. * Request may contain @@ -218,6 +176,8 @@ api_data_get2(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, + int pretty, + int use_xml, int head) { int retval = -1; @@ -226,20 +186,14 @@ api_data_get2(clicon_handle h, cbuf *cbx = NULL; yang_spec *yspec; cxobj *xret = NULL; - cxobj *xerr; - char *media_accept; - int use_xml = 0; /* By default use JSON */ + cxobj *xerr = NULL; /* malloced */ + cxobj *xe; cxobj **xvec = NULL; size_t xlen; - int pretty; int i; cxobj *x; clicon_debug(1, "%s", __FUNCTION__); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; yspec = clicon_dbspec_yang(h); if ((cbpath = cbuf_new()) == NULL) goto done; @@ -247,13 +201,19 @@ api_data_get2(clicon_handle h, clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); /* We know "data" is element pi-1 */ if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ - notfound(r); + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); clicon_debug(1, "%s path:%s", __FUNCTION__, path); if (clicon_rpc_get(h, path, &xret) < 0){ - notfound(r); + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* We get return via netconf which is complete tree from root @@ -267,9 +227,9 @@ api_data_get2(clicon_handle h, cbuf_free(cb); } #endif - /* Check if error return */ - if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ - if (api_data_get_err(h, r, xerr) < 0) + /* Check if error return XXX this needs more work */ + if ((xe = xpath_first(xret, "/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -306,7 +266,6 @@ api_data_get2(clicon_handle h, if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; } - clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); @@ -320,6 +279,8 @@ api_data_get2(clicon_handle h, cbuf_free(cbpath); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (xvec) free(xvec); return retval; @@ -331,19 +292,24 @@ api_data_get2(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) - The HEAD method is sent by the client to retrieve just the header fields - that would be returned for the comparable GET method, without the - response message-body. + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * + * The HEAD method is sent by the client to retrieve just the header fields + * that would be returned for the comparable GET method, without the + * response message-body. * Relation to netconf: none */ int api_data_head(clicon_handle h, - FCGX_Request *r, - cvec *pcvec, - int pi, - cvec *qvec) + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec, + int pretty, + int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, 1); + return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 1); } /*! REST GET method @@ -353,6 +319,8 @@ api_data_head(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode @@ -374,9 +342,11 @@ api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec) + cvec *qvec, + int pretty, + int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, 0); + return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 0); } /*! Generic REST POST method @@ -387,7 +357,13 @@ api_data_get(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @note restconf POST is mapped to edit-config create. + * See RFC8040 Sec 4.4.1 + POST: target resource type is datastore --> create a top-level resource target resource type is data resource --> create child resource @@ -412,7 +388,10 @@ api_data_post(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; enum operation_type op = OP_CREATE; @@ -425,16 +404,15 @@ api_data_post(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ - + cxobj *xret = NULL; + cxobj *xretcom = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; + char *username; + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -446,24 +424,33 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } - /* The message-body MUST contain exactly one instance of the + /* 4.4.1: The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -479,21 +466,35 @@ api_data_post(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; + cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - conflict(r); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) + goto done; + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) goto done; - badrequest(r); - goto done; + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -502,6 +503,12 @@ api_data_post(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); + if (xerr) + xml_free(xerr); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); if (xdata) @@ -571,7 +578,12 @@ match_list_keys(yang_stmt *y, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @note restconf PUT is mapped to edit-config replace. + * See RFC8040 Sec 4.5 * @example curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 * @@ -589,7 +601,10 @@ api_data_put(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; enum operation_type op = OP_REPLACE; @@ -603,16 +618,15 @@ api_data_put(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; + char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -625,24 +639,34 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -664,13 +688,19 @@ api_data_put(clicon_handle h, else { /* Check same symbol in api-path as data */ if (strcmp(xml_name(x), xml_name(xbot))){ - badrequest(r); + if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ if (y && (y->yn_keyword == Y_LIST ||y->yn_keyword == Y_LEAF_LIST)){ if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ - badrequest(r); + if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -683,21 +713,34 @@ api_data_put(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; + cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - notfound(r); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; goto ok; } - /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ - if (clicon_rpc_discard_changes(h) < 0) - goto done; - badrequest(r); + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ + if (clicon_rpc_discard_changes(h) < 0) + goto done; + if (api_return_err(h, r, xe, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -706,6 +749,12 @@ api_data_put(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); + if (xerr) + xml_free(xerr); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); if (xdata) @@ -724,15 +773,16 @@ api_data_put(clicon_handle h, * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data * Netconf: (nc:operation="merge") + * See RFC8040 Sec 4.6 */ int api_data_patch(clicon_handle h, - FCGX_Request *r, - char *api_path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data) + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data) { notimplemented(r); return 0; @@ -743,6 +793,9 @@ api_data_patch(clicon_handle h, * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] pi Offset, where path starts + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * See RFC 8040 Sec 4.7 * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 * Netconf: (nc:operation="delete") @@ -751,7 +804,9 @@ int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, - int pi) + int pi, + int pretty, + int use_xml) { int retval = -1; int i; @@ -762,6 +817,10 @@ api_data_delete(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; enum operation_type op = OP_DELETE; + cxobj *xret = NULL; + cxobj *xretcom = NULL; + cxobj *xerr = NULL; + char *username; clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -774,7 +833,8 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; @@ -783,21 +843,34 @@ api_data_delete(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; - + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - notfound(r); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ - if (clicon_rpc_discard_changes(h) < 0) - goto done; - badrequest(r); + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; + if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if (clicon_rpc_discard_changes(h) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -807,48 +880,132 @@ api_data_delete(clicon_handle h, done: if (cbx) cbuf_free(cbx); + if (xret) + xml_free(xret); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } -/*! NYI - */ -int -api_operation_get(clicon_handle h, - FCGX_Request *r, - char *path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data) -{ - return 0; -} - -/*! REST operation POST method - * @param[in] h CLIXON handle +/*! GET restconf/operations resource + * @param[in] h Clixon handle * @param[in] r Fastcgi request handle - * @param[in] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where to start pcvec + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @note We map post to edit-config create. - - POST {+restconf}/operations/ - - + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * + * @code + * curl -G http://localhost/restconf/operations + * @endcode + * RFC8040 Sec 3.3.2: + * This optional resource is a container that provides access to the + * data-model-specific RPC operations supported by the server. The + * server MAY omit this resource if no data-model-specific RPC + * operations are advertised. */ int -api_operation_post(clicon_handle h, +api_operations_get(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, int pi, cvec *qvec, char *data, - char *username) + int pretty, + int use_xml) +{ + int retval = -1; + yang_spec *yspec; + yang_stmt *ym; + yang_stmt *yc; + yang_stmt *yprefix; + char *prefix; + cbuf *cbx = NULL; + cxobj *xt = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + yspec = clicon_dbspec_yang(h); + if ((cbx = cbuf_new()) == NULL) + goto done; + cprintf(cbx, ""); + ym = NULL; + while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { + if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) + prefix = yprefix->ys_argument; + else + continue; + yc = NULL; + while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { + if (yc->ys_keyword != Y_RPC) + continue; + cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); + } + } + cprintf(cbx, ""); + clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx)); + if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) + goto done; + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + cbuf_reset(cbx); /* reuse same cbuf */ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ + goto done; + } + else{ + if (xml2json_cbuf(cbx, xt, pretty) < 0) + goto done; + } + clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + // ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cbx) + cbuf_free(cbx); + if (xt) + xml_free(xt); + return retval; +} + +/*! REST operation POST method + * @param[in] h CLIXON handle + * @param[in] r Fastcgi request handle + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where to start pcvec + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * See RFC 8040 Sec 3.6 / 4.4.2 + * @note We map post to edit-config create. + POST {+restconf}/operations/ + */ +int +api_operations_post(clicon_handle h, + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; int i; @@ -859,6 +1016,8 @@ api_operation_post(clicon_handle h, yang_stmt *youtput; cxobj *xdata = NULL; cxobj *xret = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xer; /* non-malloced error */ cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; @@ -866,37 +1025,36 @@ api_operation_post(clicon_handle h, cxobj *xinput; cxobj *xoutput; cxobj *x; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ - char *media_accept; - int use_xml = 0; /* By default return JSON */ - int pretty; cxobj *xa; - + cxobj *xe; + char *username; + cbuf *cbret = NULL; + int ret = 0; + clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - if ((media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp)) && - strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; - clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"", - __FUNCTION__, media_accept, media_content_type); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } for (i=0; i */ - if ((xinput = xpath_first(xdata, "/input")) != NULL){ + if ((xinput = xpath_first(xdata, "/input")) == NULL){ + xml_name_set(xdata, "input"); + xml_spec_set(xdata, yinput); /* needed for xml_spec_populate */ + if (yinput){ + if (xml_yang_validate_add(xdata, NULL) < 0){ + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + } + } + else{ /* Add all input under path */ x = NULL; while (xml_child_nr(xinput)){ @@ -939,57 +1127,71 @@ api_operation_post(clicon_handle h, if (xml_addsub(xbot, x) < 0) goto done; } - if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ - xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0) + if (yinput){ + xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ + if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yinput) < 0) goto done; - if (xml_apply(xinput, CX_ELMNT, + if (xml_apply(xbot, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) goto done; - if (xml_yang_validate_add(xinput, NULL) < 0) - goto done; + if (xml_yang_validate_add(xbot, NULL) < 0){ + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } } } } - /* Non-standard: add username attribute for backend ACM (RFC 6536) - * - */ - if (username){ - if ((xa = xml_new("username", xtop, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, username) < 0) - goto done; + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); + goto done; } -#ifdef obsolete - { - cxobj *xa; - char *cookie; - char *cookieval = NULL; - - if ((cookie = FCGX_GetParam("HTTP_COOKIE", r->envp)) != NULL && - get_user_cookie(cookie, "c-user", &cookieval) ==0){ - if ((xa = xml_new("id", xtop, NULL)) == NULL) + xe = NULL; + while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { + /* Look for local (client-side) restconf plugins. */ + if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, cookieval) < 0) + /* Local error: return it and quit */ + if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xer, pretty, use_xml) < 0) + goto done; + goto ok; + } + } + break; /* Just one if local */ + } + if (ret == 0){ /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; + if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xer, pretty, use_xml) < 0) goto done; - if (cookieval) - free(cookieval); + goto ok; } } -#endif - /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) - goto done; + /* Check if RPC output section */ + if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) == NULL){ + /* If the RPC operation is invoked without errors and if the "rpc" or + * "action" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "\r\n"); + goto ok; + } if ((cbx = cbuf_new()) == NULL) goto done; - xoutput=xpath_first(xret, "/"); - xml_name_set(xoutput, "output"); - if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL && - xoutput){ - + if ((xoutput=xpath_first(xret, "/")) != NULL){ + xml_name_set(xoutput, "output"); +#if 0 clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); +#endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) @@ -1012,7 +1214,9 @@ api_operation_post(clicon_handle h, else if (xml2json_cbuf(cbx, xoutput, pretty) < 0) goto done; - clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); +#if 1 + clicon_debug(1, "%s cbx:%s", __FUNCTION__, cbuf_get(cbx)); +#endif FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); } @@ -1026,7 +1230,11 @@ api_operation_post(clicon_handle h, xml_free(xtop); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (cbx) cbuf_free(cbx); + if (cbret) + cbuf_free(cbret); return retval; } diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 659a2c6c..11daaa26 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -46,27 +46,31 @@ */ int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec); + cvec *qvec, int pretty, int use_xml); int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec); + cvec *qvec, int pretty, int use_xml); int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data); + cvec *qvec, char *data, + int pretty, int use_xml, int parse_xml); int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data); + cvec *qvec, char *data, + int pretty, int use_xml, int parse_xml); int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data); -int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi); +int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, + int pretty, int use_xml); -int api_operation_get(clicon_handle h, FCGX_Request *r, - char *path, - cvec *pcvec, int pi, cvec *qvec, char *data); - -int api_operation_post(clicon_handle h, FCGX_Request *r, +int api_operations_get(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, int pi, cvec *qvec, char *data, - char *username); + int pretty, int use_xml); + +int api_operations_post(clicon_handle h, FCGX_Request *r, + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data, + int pretty, int use_xml, int parse_xml); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp deleted file mode 100644 index feed22aa..00000000 --- a/clixon.conf.cpp.cpp +++ /dev/null @@ -1,129 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# This file is part of CLIXON -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Alternatively, the contents of this file may be used under the terms of -# the GNU General Public License Version 3 or later (the "GPL"), -# in which case the provisions of the GPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of the GPL, and not to allow others to -# use your version of this file under the terms of Apache License version 2, -# indicate your decision by deleting the provisions above and replace them with -# the notice and other provisions required by the GPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the Apache License version 2 or the GPL. -# -# ***** END LICENSE BLOCK ***** -# - -# -# CLIXON options - Default values -# The origin of this file is run a _first_ time through a pre-processor at -# clixon make install time causing autoconf constants (such as "prefix" and -# "localstatedir") to be replaced with their installed values. -# It should be run a _second_ time as a part of installation of the application, -# in case clixon.mk is included in the application include file, and -# "$(APPNAME).conf" rule is accessed. -# -# See clicon_tutorial for more documentation - -# Location of configuration-file for default values (this file) -CLICON_CONFIGFILE sysconfdir/APPNAME.conf - -# Location of YANG module and submodule files. -CLICON_YANG_DIR prefix/share/APPNAME/yang - -# Main yang module or absolute filename. If module then search as follows: -# /[@] -# CLICON_YANG_MODULE_MAIN clicon - -# Option used to construct initial yang file: -# [@] -CLICON_YANG_MODULE_REVISION - -# Location of backend .so plugins -CLICON_BACKEND_DIR libdir/APPNAME/backend - -# Location of netconf (frontend) .so plugins -CLICON_NETCONF_DIR libdir/APPNAME/netconf - -# Location of restconf (frontend) .so plugins -CLICON_RESTCONF_DIR libdir/APPNAME/restconf - -# Location of cli frontend .so plugins -CLICON_CLI_DIR libdir/APPNAME/cli - -# Location of frontend .cli cligen spec files -CLICON_CLISPEC_DIR libdir/APPNAME/clispec - -# Enabled uses "startup" configuration on boot -CLICON_USE_STARTUP_CONFIG 0 - -# Address family for communicating with clixon_backend (UNIX|IPv4|IPv6) -CLICON_SOCK_FAMILY UNIX - -# If family above is AF_UNIX: Unix socket for communicating with clixon_backend -# If family above is AF_INET: IPv4 address -CLICON_SOCK localstatedir/APPNAME/APPNAME.sock - -# Inet socket port for communicating with clixon_backend (only IPv4|IPv6) -CLICON_SOCK_PORT 4535 - -# Process-id file -CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile - -# Group membership to access clixon_backend unix socket -# CLICON_SOCK_GROUP clicon - -# Set if all configuration changes are committed directly, commit command unnecessary -# CLICON_AUTOCOMMIT 0 - -# Name of master plugin (both frontend and backend). Master plugin has special -# callbacks for frontends. See clicon user manual for more info. -# CLICON_MASTER_PLUGIN master - -# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file -# CLICON_CLI_MODE base - -# Generate code for CLI completion of existing db symbols. Add name="myspec" in -# datamodel spec and reference as @myspec. -# CLICON_CLI_GENMODEL 1 - -# Generate code for CLI completion of existing db symbols -# CLICON_CLI_GENMODEL_COMPLETION 1 - -# How to generate and show CLI syntax: VARS|ALL -# CLICON_CLI_GENMODEL_TYPE VARS - -# Directory where "running", "candidate" and "startup" are placed -CLICON_XMLDB_DIR localstatedir/APPNAME - -# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch]) -CLICON_XMLDB_PLUGIN libdir/xmldb/text.so - -# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored -# CLICON_CLI_VARONLY 1 - -# Set to 0 if you want CLI to wrap to next line. -# Set to 1 if you want CLI to scroll sideways when approaching right margin -# CLICON_CLI_LINESCROLLING 1 - -# FastCGI unix socket. Should be specified in webserver -# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock; -CLICON_RESTCONF_PATH /www-data/fastcgi_restconf.sock - diff --git a/configure b/configure index 6701990d..3e4fb4b6 100755 --- a/configure +++ b/configure @@ -632,7 +632,6 @@ CPP OBJEXT EXEEXT ac_ct_CC -with_keyvalue with_restconf RANLIB AR @@ -640,6 +639,7 @@ EXE_SUFFIX SH_SUFFIX AR_SUFFIX OBJ_SUFFIX +INSTALLFLAGS CPPFLAGS INCLUDES LDFLAGS @@ -707,8 +707,6 @@ ac_user_opts=' enable_option_checking with_cligen with_restconf -with_keyvalue -with_qdbm with_configfile ' ac_precious_vars='build_alias @@ -1346,8 +1344,6 @@ Optional Packages: --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-cligen=dir Use CLIGEN here --without-restconf disable support for restconf - --with-keyvalue enable support for key-value xmldb datastore - --with-qdbm=dir Use QDBM here, if keyvalue --with-configfile=FILE set default path to config file Some influential environment variables: @@ -2146,13 +2142,14 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -# Default CFLAGS unless set by environment. -: ${CFLAGS="-O2"} +# Default CFLAGS unless set by environment +: ${CFLAGS="-O2 -Wall"} +: ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="5" +CLIXON_VERSION_MINOR="6" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then @@ -2348,8 +2345,8 @@ test -n "$target_alias" && + # If yes, compile apps/restconf - # If yes, compile datastore/keyvalue # ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -3283,12 +3280,12 @@ CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" { $as_echo "$as_me:${as_lineno-$LINENO}: result: compiler is $CC" >&5 $as_echo "compiler is $CC" >&6; } -CFLAGS="${CFLAGS} -Wall" - { $as_echo "$as_me:${as_lineno-$LINENO}: result: CPPFLAGS is $CPPFLAGS" >&5 $as_echo "CPPFLAGS is $CPPFLAGS" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: CFLAGS is $CFLAGS" >&5 $as_echo "CFLAGS is $CFLAGS" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: INSTALLFLAGS is $INSTALLFLAGS" >&5 +$as_echo "INSTALLFLAGS is $INSTALLFLAGS" >&6; } for ac_prog in 'bison -y' byacc do @@ -3946,107 +3943,6 @@ fi fi -# This is for keyvalue datastore (and qdbm) - -# Check whether --with-keyvalue was given. -if test "${with_keyvalue+set}" = set; then : - withval=$with_keyvalue; -else - with_keyvalue=no -fi - -if test "x${with_keyvalue}" == xyes; then - # This is for qdbm - -# Check whether --with-qdbm was given. -if test "${with_qdbm+set}" = set; then : - withval=$with_qdbm; -fi - - if test "${with_qdbm}"; then - echo "Using QDBM here: ${with_qdbm}" - CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" - LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" - fi - # Problem: depot.h may be in qdbm/depot.h. - for ac_header in depot.h -do : - ac_fn_c_check_header_mongrel "$LINENO" "depot.h" "ac_cv_header_depot_h" "$ac_includes_default" -if test "x$ac_cv_header_depot_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_DEPOT_H 1 -_ACEOF - -else - for ac_header in qdbm/depot.h -do : - ac_fn_c_check_header_mongrel "$LINENO" "qdbm/depot.h" "ac_cv_header_qdbm_depot_h" "$ac_includes_default" -if test "x$ac_cv_header_qdbm_depot_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_QDBM_DEPOT_H 1 -_ACEOF - -else - as_fn_error $? "libqdbm-dev required" "$LINENO" 5 -fi - -done - -fi - -done - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dpopen in -lqdbm" >&5 -$as_echo_n "checking for dpopen in -lqdbm... " >&6; } -if ${ac_cv_lib_qdbm_dpopen+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-lqdbm $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char dpopen (); -int -main () -{ -return dpopen (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_qdbm_dpopen=yes -else - ac_cv_lib_qdbm_dpopen=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_qdbm_dpopen" >&5 -$as_echo "$ac_cv_lib_qdbm_dpopen" >&6; } -if test "x$ac_cv_lib_qdbm_dpopen" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_LIBQDBM 1 -_ACEOF - - LIBS="-lqdbm $LIBS" - -else - as_fn_error $? "libqdbm-dev required" "$LINENO" 5 -fi - - ac_config_files="$ac_config_files datastore/keyvalue/Makefile" - -fi - # Set default config file location # Check whether --with-configfile was given. @@ -4311,7 +4207,6 @@ _ACEOF -# See also datastore/keyvalue/Makefile in with_keyvalue clause above ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile extras/rpm/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/text/Makefile yang/Makefile doc/Makefile" cat >confcache <<\_ACEOF @@ -5005,7 +4900,6 @@ do case $ac_config_target in "include/clixon_config.h") CONFIG_HEADERS="$CONFIG_HEADERS include/clixon_config.h" ;; "lib/clixon/clixon.h") CONFIG_HEADERS="$CONFIG_HEADERS lib/clixon/clixon.h" ;; - "datastore/keyvalue/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/keyvalue/Makefile" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "lib/Makefile") CONFIG_FILES="$CONFIG_FILES lib/Makefile" ;; "lib/src/Makefile") CONFIG_FILES="$CONFIG_FILES lib/src/Makefile" ;; diff --git a/configure.ac b/configure.ac index 7b396216..2990f5d6 100644 --- a/configure.ac +++ b/configure.ac @@ -38,13 +38,14 @@ AC_INIT(lib/clixon/clixon.h.in) -# Default CFLAGS unless set by environment. -: ${CFLAGS="-O2"} +# Default CFLAGS unless set by environment +: ${CFLAGS="-O2 -Wall"} +: ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="5" +CLIXON_VERSION_MINOR="6" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then @@ -77,6 +78,7 @@ AC_SUBST(CFLAGS) AC_SUBST(LDFLAGS) AC_SUBST(INCLUDES) AC_SUBST(CPPFLAGS) +AC_SUBST(INSTALLFLAGS) AC_SUBST(LIBS) AC_SUBST(OBJ_SUFFIX) AC_SUBST(AR_SUFFIX) @@ -85,7 +87,6 @@ AC_SUBST(EXE_SUFFIX) AC_SUBST(AR) AC_SUBST(RANLIB) AC_SUBST(with_restconf) # If yes, compile apps/restconf -AC_SUBST(with_keyvalue) # If yes, compile datastore/keyvalue # AC_PROG_CC() AC_PROG_CPP @@ -94,10 +95,9 @@ CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" AC_MSG_RESULT(compiler is $CC) -CFLAGS="${CFLAGS} -Wall" - AC_MSG_RESULT(CPPFLAGS is $CPPFLAGS) -AC_MSG_RESULT(CFLAGS is $CFLAGS) +AC_MSG_RESULT(CFLAGS is $CFLAGS) +AC_MSG_RESULT(INSTALLFLAGS is $INSTALLFLAGS) AC_PROG_YACC AC_PROG_LEX @@ -142,25 +142,6 @@ if test "x${with_restconf}" == xyes; then AC_CHECK_LIB(fcgi, FCGX_Init,, AC_MSG_ERROR([libfcgi-dev missing])) fi -# This is for keyvalue datastore (and qdbm) -AC_ARG_WITH([keyvalue], - [AS_HELP_STRING([--with-keyvalue],[enable support for key-value xmldb datastore])], - [], - [with_keyvalue=no]) -if test "x${with_keyvalue}" == xyes; then - # This is for qdbm - AC_ARG_WITH(qdbm, [ --with-qdbm=dir Use QDBM here, if keyvalue ] ) - if test "${with_qdbm}"; then - echo "Using QDBM here: ${with_qdbm}" - CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" - LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" - fi - # Problem: depot.h may be in qdbm/depot.h. - AC_CHECK_HEADERS(depot.h,,[AC_CHECK_HEADERS(qdbm/depot.h,,AC_MSG_ERROR(libqdbm-dev required))]) - AC_CHECK_LIB(qdbm, dpopen,, AC_MSG_ERROR(libqdbm-dev required)) - AC_CONFIG_FILES(datastore/keyvalue/Makefile) -fi - # Set default config file location AC_ARG_WITH([configfile], [AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])], @@ -193,7 +174,6 @@ AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${DEFAULT_CONFIG}",[Location for apps AH_BOTTOM([#include ]) -# See also datastore/keyvalue/Makefile in with_keyvalue clause above AC_OUTPUT(Makefile lib/Makefile lib/src/Makefile diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 6e5ce1fe..16beab79 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -44,7 +44,8 @@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ CC = @CC@ -CFLAGS = @CFLAGS@ +CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ with_restconf = @with_restconf@ @@ -68,9 +69,6 @@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/inclu SUBDIRS = text -ifeq ($(with_keyvalue),yes) -SUBDIRS += keyvalue -endif .PHONY: all clean depend install $(SUBDIRS) @@ -104,8 +102,8 @@ install-include: do (cd $$i ; $(MAKE) $(MFLAGS) $@)||exit 1; done; install: $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) for i in $(SUBDIRS); \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 89110ff5..bdcd4ac5 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -33,11 +33,11 @@ * Examples: -./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip get / +./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get / -sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' +sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' -sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge / 'eth0true' +sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / 'eth0true' */ @@ -124,6 +124,7 @@ main(int argc, char **argv) cxobj *xt = NULL; int i; char *xpath; + cbuf *cbret = NULL; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); @@ -261,7 +262,9 @@ main(int argc, char **argv) goto done; if (xml_rootchild(xt, 0, &xt) < 0) goto done; - if (xmldb_put(h, db, op, xt) < 0) + if ((cbret = cbuf_new()) == NULL) + goto done; + if (xmldb_put(h, db, op, xt, cbret) < 0) goto done; } else if (strcmp(cmd, "copy")==0){ @@ -325,6 +328,8 @@ main(int argc, char **argv) if (xmldb_plugin_unload(h) < 0) goto done; done: + if (cbret) + cbuf_free(cbret); if (xt) xml_free(xt); if (h) diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in index b18501b7..19aa6fe6 100644 --- a/datastore/keyvalue/Makefile.in +++ b/datastore/keyvalue/Makefile.in @@ -47,6 +47,7 @@ sysconfdir = @sysconfdir@ VPATH = @srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ DATASTORE = keyvalue @@ -80,8 +81,8 @@ distclean: clean $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< install: $(PLUGIN) - install -d $(DESTDIR)$(libdir)/xmldb - install $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -d -m 0755 $(DESTDIR)$(libdir)/xmldb + install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 018c86fd..aa529477 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -211,7 +211,7 @@ append_listkeys(cbuf *ckey, xml_name(xt), keyname); goto done; } - if (percent_encode(xml_body(xkey), &bodyenc) < 0) + if (uri_percent_encode(xml_body(xkey), &bodyenc) < 0) goto done; if (i++) cprintf(ckey, ","); @@ -312,7 +312,7 @@ get(char *dbname, restval++; } if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name, 0)) == NULL){ + if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -328,7 +328,7 @@ get(char *dbname, * If xml element is a leaf-list, then the next element is expected to * be a value */ - if (percent_decode(restval, &argdec) < 0) + if (uri_percent_decode(restval, &argdec) < 0) goto done; if ((xc = xml_find(x, name))==NULL || (xb = xml_find(xc, argdec))==NULL){ @@ -373,7 +373,7 @@ get(char *dbname, if (j>=nvalvec) break; arg = valvec[j++]; - if (percent_decode(arg, &argdec) < 0) + if (uri_percent_decode(arg, &argdec) < 0) goto done; cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec); free(argdec); @@ -391,7 +391,7 @@ get(char *dbname, break; arg = valvec[j++]; keyname = cv_string_get(cvi); - if (percent_decode(arg, &argdec) < 0) + if (uri_percent_decode(arg, &argdec) < 0) goto done; if (create_keyvalues(xc, ykey, @@ -681,7 +681,7 @@ put(char *dbfile, goto done; break; case Y_LEAF_LIST: - if (percent_encode(body, &bodyenc) < 0) + if (uri_percent_encode(body, &bodyenc) < 0) goto done; cprintf(cbxk, "=%s", bodyenc); break; @@ -799,7 +799,7 @@ kv_put(xmldb_handle xh, } // clicon_log(LOG_WARNING, "%s", __FUNCTION__); while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((ys = yang_find_topnode(yspec, xml_name(x), 0)) == NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x), YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); goto done; } diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index ad24c6e9..cdc2adb5 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -47,6 +47,7 @@ HOST_VENDOR = @host_vendor@ VPATH = @srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ DATASTORE = text @@ -84,8 +85,8 @@ distclean: clean $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< install: $(PLUGIN) - install -d $(DESTDIR)$(libdir)/xmldb - install $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -d -m 0755 $(DESTDIR)$(libdir)/xmldb + install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 739e2256..9bc96f46 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -560,6 +560,7 @@ text_get(xmldb_handle xh, * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ @@ -568,7 +569,8 @@ text_modify(cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1, - enum operation_type op) + enum operation_type op, + cbuf *cbret) { int retval = -1; char *opstr; @@ -594,8 +596,9 @@ text_modify(cxobj *x0, switch(op){ case OP_CREATE: if (x0){ - clicon_err(OE_XML, 0, "Object to create already exists"); - goto done; + if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) + goto done; + goto ok; } case OP_NONE: /* fall thru */ case OP_MERGE: @@ -631,8 +634,9 @@ text_modify(cxobj *x0, break; case OP_DELETE: if (x0==NULL){ - clicon_err(OE_XML, 0, "Object to delete does not exist"); - goto done; + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; } case OP_REMOVE: /* fall thru */ if (x0){ @@ -647,8 +651,9 @@ text_modify(cxobj *x0, switch(op){ case OP_CREATE: if (x0){ - clicon_err(OE_XML, 0, "Object to create already exists"); - goto done; + if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) + goto done; + goto ok; } case OP_REPLACE: /* fall thru */ if (x0){ @@ -704,14 +709,18 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op) < 0) + if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; } break; case OP_DELETE: if (x0==NULL){ - clicon_err(OE_XML, 0, "Object to delete does not exist"); - goto done; + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; } case OP_REMOVE: /* fall thru */ if (x0) @@ -721,27 +730,29 @@ text_modify(cxobj *x0, break; } /* CONTAINER switch op */ } /* else Y_CONTAINER */ - // ok: xml_sort(x0p, NULL); + ok: retval = 0; done: if (x0vec) free(x0vec); return retval; -} +} /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * @see text_modify */ static int text_modify_top(cxobj *x0, cxobj *x1, yang_spec *yspec, - enum operation_type op) + enum operation_type op, + cbuf *cbret) { int retval = -1; char *x1cname; /* child name */ @@ -759,7 +770,7 @@ text_modify_top(cxobj *x0, if (xml_operation(opstr, &op) < 0) goto done; /* Special case if x1 is empty, top-level only */ - if (!xml_child_nr(x1)){ + if (xml_child_nr(x1) == 0){ if (xml_child_nr(x0)) /* base tree not empty */ switch(op){ case OP_DELETE: @@ -775,31 +786,43 @@ text_modify_top(cxobj *x0, else /* base tree empty */ switch(op){ case OP_DELETE: - clicon_err(OE_XML, 0, "Object to delete does not exist"); + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; break; default: break; } } + /* Special case top-level replace */ + if (op == OP_REPLACE || op == OP_DELETE){ + x0c = NULL; + while ((x0c = xml_child_i(x0, 0)) != 0) + xml_purge(x0c); + } /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ + clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", x1, x1cname); goto done; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0) + if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; } + ok: retval = 0; done: return retval; -} +} /* text_modify_top */ /*! For containers without presence and no children, remove * @param[in] x XML tree node @@ -846,7 +869,8 @@ int text_put(xmldb_handle xh, const char *db, enum operation_type op, - cxobj *x1) + cxobj *x1, + cbuf *cbret) { int retval = -1; struct text_handle *th = handle(xh); @@ -857,7 +881,15 @@ text_put(xmldb_handle xh, yang_spec *yspec; cxobj *x0 = NULL; struct db_element *de = NULL; + int cbretlocal = 0; /* Set if cbret is NULL on entry */ + if (cbret == NULL){ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cbretlocal++; + } if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; @@ -922,8 +954,11 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(x0, x1, yspec, op) < 0) + if (text_modify_top(x0, x1, yspec, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; /* Remove NONE nodes if all subs recursively are also NONE */ if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) @@ -973,8 +1008,11 @@ text_put(xmldb_handle xh, } else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) goto done; + ok: retval = 0; done: + if (cbretlocal && cbret) + cbuf_free(cbret); if (f != NULL) fclose(f); if (dbfile) @@ -1381,7 +1419,7 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn) < 0) + if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) goto done; } else diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index 12940f89..19e9351d 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -40,7 +40,7 @@ * Prototypes */ int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); +int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); int text_dump(FILE *f, char *dbfilename, char *rxkey); int text_copy(xmldb_handle h, const char *from, const char *to); int text_lock(xmldb_handle h, const char *db, int pid); diff --git a/doc/Doxyfile b/doc/Doxyfile index 98375856..8dd946de 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -743,7 +743,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl ../datastore +INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl ../datastore ../datastore/text # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/doc/Doxyfile.graphs b/doc/Doxyfile.graphs index 03ca9505..7eaf3ef0 100644 --- a/doc/Doxyfile.graphs +++ b/doc/Doxyfile.graphs @@ -743,7 +743,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore +INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore ../datastore/text # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/doc/FAQ.md b/doc/FAQ.md index ee04a409..949f94ce 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -1,4 +1,4 @@ -# Clixon FAQ +i# Clixon FAQ ## What is Clixon? @@ -24,7 +24,7 @@ Clixon is written in C. The plugins are written in C. The CLI specification uses cligen (http://cligen.se) ## How to best understand Clixon? -Run the ietf yang routing example, in the example directory. +Run the Clixon example, in the example directory. ## How do you build and install Clixon (and the example)? Clixon: @@ -56,14 +56,14 @@ Build using 'make doc' and aim your browser at doc/html/index.html or use the web resource: http://clicon.org/ref/index.html ## How do you run the example? -- Start a backend server: 'clixon_backend -Ff /usr/local/etc/routing.xml' -- Start a cli session: clixon_cli -f /usr/local/etc/routing.xml -- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.xml +- Start a backend server: 'clixon_backend -Ff /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 ## How is configuration data stored? Configuration data is stored in an XML datastore. The default is a text-based datastore. In the example the datastore are regular files found in -/usr/local/var/routing/. +/usr/local/var/example/. ## What is validate and commit? Clixon follows netconf in its validate and commit semantics. @@ -79,15 +79,15 @@ is the core functionality of a clixon system. Clixon options are stored in an XML configuration file. The default configuration file is /usr/local/etc/clixon.xml. The example -configuration file is installed at /usr/local/etc/routing.xml. The +configuration file is installed at /usr/local/etc/example.xml. The YANG specification for the configuration file is clixon-config.yang. You can change where CLixon looks for the configuration FILE as follows: - # Provide -f FILE option when starting a program (eg clixon_backend -f FILE) - # Provide --with-configfile=FILE when configuring - # Provide --with-sysconfig= when configuring, then FILE is /clixon.xml - # Provide --sysconfig= when configuring then FILE is /etc/clixon.xml - # FILE is /usr/local/etc/clixon.xml + - Provide -f FILE option when starting a program (eg clixon_backend -f FILE) + - Provide --with-configfile=FILE when configuring + - Provide --with-sysconfig= when configuring, then FILE is /clixon.xml + - Provide --sysconfig= when configuring then FILE is /etc/clixon.xml + - FILE is /usr/local/etc/clixon.xml ## Can I run Clixon as docker containers? Yes, the example works as docker containers as well. backend and cli needs a @@ -109,12 +109,12 @@ You may also push the containers with 'make push' but you may then consider chan As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. Example: - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/routing.xml + echo "]]>]]>" | 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/routing.xml + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml ``` and then invoke it from a client using ``` @@ -149,7 +149,7 @@ cli> ``` or via netconf: ``` -clixon_netconf -qf /usr/local/etc/routing.xml +clixon_netconf -qf /usr/local/etc/example.xml ROUTING]]>]]> ]]>]]> Routing notification]]>]]> @@ -194,18 +194,39 @@ clixon_backend ... -c extra.xml ``` 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 routing_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? -- routing.xml - Change the configuration file +See [../apps/example] +- example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. -- routing_cli.cli - Change the fixed part of the CLI commands -- routing_cli.c - Cli C-commands are placed here. -- routing_backend.c - Commit and validate functions. -- routing_netconf.c - Modify semantics of netconf commands. +- example_cli.cli - Change the fixed part of the CLI commands +- example_cli.c - Cli C-commands are placed here. +- example_backend.c - Commit and validate functions. +- example_netconf.c - Netconf plugin +- example_restconf.c - Add restconf authentication, etc. + +## How is a plugin initiated? +Each plugin is initiated with an API struct followed by a plugin init function as follows: +``` + static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, + plugin_start, + ... /* more functions here */ + } + clixon_plugin_api * + clixon_plugin_init(clicon_handle h) + { + ... + return &api; /* Return NULL on error */ + } +``` +For more info see [../example/README.md] + ## How do I write a commit function? -In the example, you write a commit function in routing_backend.c. +In the example, you write a commit function in example_backend.c. Every time a commit is made, transaction_commit() is called in the backend. It has a 'transaction_data td' argument which is used to fetch information on added, deleted and changed entries. You access this @@ -235,9 +256,9 @@ More are found in the doxygen reference. ## How do I write a CLI callback function? -1. You add an entry in routing_cli.cli +1. You add an entry in example_cli.cli > example("This is a comment") ("This is a variable"), mycallback("myarg"); -2. Then define a function in routing_cli.c +2. Then define a function in example_cli.c > mycallback(clicon_handle h, cvec *cvv, cvec *arv) where 'cvv' contains the value of the variable 'var' and 'argv' contains the string "myarg". @@ -283,10 +304,10 @@ implement the RFC, you need to register an RPC callback in the backend plugin: Example: ``` int -plugin_init(clicon_handle h) +clixon_plugin_init(clicon_handle h) { ... - backend_rpc_cb_register(h, fib_route, NULL, "fib-route"); + rpc_callback_register(h, fib_route, NULL, "fib-route"); ... } ``` @@ -295,12 +316,31 @@ And then define the callback itself: static int fib_route(clicon_handle h, /* Clicon handle */ cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; } ``` -Here, the callback is over-simplified. \ No newline at end of file +Here, the callback is over-simplified. + +## How do I write an authentication callback? + +A restconf call may need to be authenticated. +You can specify an authentication callback for restconf as follows: +``` +int +plugin_credentials(clicon_handle h, + void *arg) +{ + FCGX_Request *r = (FCGX_Request *)arg; + ... + clicon_username_set(h, user); +``` + +To authenticate, the callback needs to return the value 1 and supply a username. + +See [../apps/example/example_restconf.c] plugin_credentials() for +an example of HTTP basic auth. diff --git a/etc/Makefile.in b/etc/Makefile.in index f124dc52..263840df 100644 --- a/etc/Makefile.in +++ b/etc/Makefile.in @@ -48,8 +48,8 @@ distclean: clean rm -f Makefile *~ .depend clixonrc install: clixonrc - install -m 755 -d $(DESTDIR)$(sysconfdir) - install -m 755 clixonrc $(DESTDIR)$(sysconfdir) + install -m 0755 -d $(DESTDIR)$(sysconfdir) + install -m 0644 clixonrc $(DESTDIR)$(sysconfdir) install-include: diff --git a/example/Makefile.in b/example/Makefile.in index f56f6bf0..0be6a8aa 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -38,27 +38,29 @@ bindir = @bindir@ includedir = @includedir@ datarootdir = @datarootdir@ -APPNAME = routing +APPNAME = example CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ INCLUDES = -I$(includedir) @INCLUDES@ BE_PLUGIN = $(APPNAME)_backend.so +BE2_PLUGIN = $(APPNAME)_backend_nacm.so CLI_PLUGIN = $(APPNAME)_cli.so NETCONF_PLUGIN = $(APPNAME)_netconf.so +RESTCONF_PLUGIN = $(APPNAME)_restconf.so -PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) +PLUGINS = $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) all: $(PLUGINS) -# Note: clixon.mk has a rule for: -# $(APPNAME.conf) +# Note: clixon.mk has rules for clixon_DBSPECDIR, clixon_SYSCONFDIR, etc used below -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk -CLISPECS = routing_cli.cli +CLISPECS = $(APPNAME)_cli.cli -#YANGSPECS = $(APPNAME).yang +YANGSPECS = $(APPNAME).yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-interfaces@2014-05-08.yang @@ -67,28 +69,39 @@ YANGSPECS += ietf-routing@2014-10-26.yang YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipsec@2016-03-09.yang -YANGSPECS += example.yang # Backend plugin -BE_SRC = routing_backend.c +BE_SRC = $(APPNAME)_backend.c BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) - $(CC) -shared -o $@ -lc $< + $(CC) -Wall -shared -o $@ -lc $< + +# Secondary NACM backend plugin +BE2_SRC = $(APPNAME)_backend_nacm.c +BE2_OBJ = $(BE2_SRC:%.c=%.o) +$(BE2_PLUGIN): $(BE2_OBJ) + $(CC) -Wall -shared -o $@ -lc $< # CLI frontend plugin -CLI_SRC = routing_cli.c +CLI_SRC = $(APPNAME)_cli.c CLI_OBJ = $(CLI_SRC:%.c=%.o) $(CLI_PLUGIN): $(CLI_OBJ) - $(CC) -shared -o $@ -lc $^ + $(CC) -Wall -shared -o $@ -lc $^ # NETCONF frontend plugin -NETCONF_SRC = routing_netconf.c +NETCONF_SRC = $(APPNAME)_netconf.c NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o) $(NETCONF_PLUGIN): $(NETCONF_OBJ) - $(CC) -shared -o $@ -lc $^ + $(CC) -Wall -shared -o $@ -lc $^ -SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC) -OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ) +# RESTCONF frontend plugin +RESTCONF_SRC = $(APPNAME)_restconf.c +RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o) +$(RESTCONF_PLUGIN): $(RESTCONF_OBJ) + $(CC) -Wall -shared -o $@ -lc $^ + +SRC = $(BE_SRC) $(BE2_SRC) $(CLI_SRC) $(NETCONF_SRC) $(RESTCONF_SRC) +OBJS = $(BE_OBJ) $(BE2_OBJ) $(CLI_OBJ) $(NETCONF_OBJ) $(RESTCONF_OBJ) clean: rm -f $(PLUGINS) $(OBJS) @@ -98,20 +111,22 @@ distclean: clean rm -f Makefile *~ .depend (cd docker && $(MAKE) $(MFLAGS) $@) -install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).xml - install -d $(DESTDIR)$(clixon_SYSCONFDIR) - install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) - install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang - install $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang - install -d $(DESTDIR)$(clixon_LIBDIR)/cli - install $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli; - install -d $(DESTDIR)$(clixon_LIBDIR)/backend - install $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend; - install -d $(DESTDIR)$(clixon_LIBDIR)/netconf - install $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf; - install -d $(DESTDIR)$(clixon_LIBDIR)/clispec - install $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec; - install -d $(DESTDIR)$(clixon_LOCALSTATEDIR) +install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml + install -d -m 0755 $(DESTDIR)$(clixon_SYSCONFDIR) + install -m 0644 $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) + install -d -m 0755 $(DESTDIR)$(clixon_DBSPECDIR)/yang + install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/cli + install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/backend + install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/netconf + install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/restconf + install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/clispec + install -m 0644 $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec + install -d -m 0755 $(DESTDIR)$(clixon_LOCALSTATEDIR) (cd docker && $(MAKE) $(MFLAGS) $@) uninstall: diff --git a/example/README.md b/example/README.md index e2cf9fe6..cafd796f 100644 --- a/example/README.md +++ b/example/README.md @@ -1,4 +1,20 @@ -# Clixon yang routing example +# Clixon example + +This directory contains a Clixon example which includes a simple +routing example. It contains the following files: +* example.xml The configuration file. See yang/clixon-config@.yang for all available fields. +* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. +* 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_backend.c Backend callback plugin including example of: + * transaction callbacks (validate/commit), + * notification, + * rpc handler + * state-data handler, ie non-config data +* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. +* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback +* example_netconf.c Netconf callback plugin +* Makefile.in Example makefile where plugins are built and installed ## Compile and run ``` @@ -7,15 +23,23 @@ ``` Start backend: ``` - clixon_backend -f /usr/local/etc/routing.xml -I + clixon_backend -f /usr/local/etc/example.xml -I ``` Edit cli: ``` - clixon_cli -f /usr/local/etc/routing.xml + clixon_cli -f /usr/local/etc/example.xml ``` Send netconf command: ``` - clixon_netconf -f /usr/local/etc/routing.xml + clixon_netconf -f /usr/local/etc/example.xml +``` +Start clixon restconf daemon +``` +> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data +``` +Send restconf command +``` + curl -G http://127.0.0.1/restconf/data ``` ## Setting data example using netconf @@ -65,6 +89,38 @@ Routing notification ... ``` +## Initializing a plugin + +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. +The content of the API struct is different depending on what kind of plugin it is. +The plugin init function may also include registering RPC functions, see below is for a backend. +``` +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, + plugin_start, + plugin_exit, + .ca_reset=plugin_reset,/* reset */ + .ca_statedata=plugin_statedata, /* statedata */ + .ca_trans_begin=NULL, /* trans begin */ + .ca_trans_validate=transaction_validate,/* trans validate */ + .ca_trans_complete=NULL, /* trans complete */ + .ca_trans_commit=transaction_commit, /* trans commit */ + .ca_trans_end=NULL, /* trans end */ + .ca_trans_abort=NULL /* trans abort */ +}; + +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + /* Optional callback registration for RPC calls */ + rpc_callback_register(h, fib_route, NULL, "fib-route"); + /* Return plugin API */ + return &api; /* Return NULL on error */ +} +``` + ## Operation data Clixon implements Yang RPC operations by an extension mechanism. The @@ -95,18 +151,18 @@ In the backend, a callback is registered (fib_route()) which handles the RPC. static int fib_route(clicon_handle h, cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; } int -plugin_init(clicon_handle h) +clixon_plugin_init(clicon_handle h) { ... - backend_rpc_cb_register(h, fib_route, NULL, "fib-route"); + rpc_callback_register(h, fib_route, NULL, "fib-route"); ... } ``` @@ -114,7 +170,7 @@ plugin_init(clicon_handle h) Netconf 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 @@ -126,8 +182,15 @@ 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: adm1, wilma, and guest, according to the examples in Appendix A in the RFC. +* A NACM backend plugin reporting the mandatory NACM state variables. + ## Run as docker container + +(Note not updated) ``` cd docker # look in README diff --git a/example/example.xml b/example/example.xml new file mode 100644 index 00000000..337048bb --- /dev/null +++ b/example/example.xml @@ -0,0 +1,20 @@ + + /usr/local/etc/example.xml + /usr/local/share/example/yang + example + example + /usr/local/lib/example/backend + /usr/local/lib/example/netconf + /usr/local/lib/example/restconf + /usr/local/lib/example/cli + /usr/local/lib/example/clispec + /usr/local/var/example/example.sock + /usr/local/var/example/example.pidfile + 1 + VARS + /usr/local/var/example + /usr/local/lib/xmldb/text.so + 0 + init + disabled + diff --git a/example/example.yang b/example/example.yang index 3c6a6f3a..f9159228 100644 --- a/example/example.yang +++ b/example/example.yang @@ -1,10 +1,30 @@ module example { + prefix ex; import ietf-ip { - prefix ip; + prefix ip; } import ietf-routing { - prefix rt; + prefix rt; + } + import ietf-netconf-acm { + prefix nacm; } description - "Example code that includes ietf-ip and ietf-routing"; + "Example code that includes ietf-ip and ietf-routing"; + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } diff --git a/example/routing_backend.c b/example/example_backend.c similarity index 72% rename from example/routing_backend.c rename to example/example_backend.c index 59c7388b..495ad14f 100644 --- a/example/routing_backend.c +++ b/example/example_backend.c @@ -77,12 +77,14 @@ transaction_commit(clicon_handle h, int i; size_t len; + clicon_debug(1, "%s", __FUNCTION__); /* Get all added i/fs */ if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0) return -1; if (debug) for (i=0; i */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, "" "ipv4" @@ -134,27 +138,34 @@ fib_route(clicon_handle h, /* Clicon handle */ return 0; } -/*! Smallest possible RPC declaration for test */ -static int -empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ -{ - cprintf(cbret, ""); - return 0; -} - -/*! IETF Routing route-count rpc */ +/*! IETF Routing route-count rpc + * @see ietf-routing@2014-10-26.yang (route-count) + */ static int route_count(clicon_handle h, cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, + void *regarg) /* Argument given at register */ { - cprintf(cbret, ""); + cprintf(cbret, "42"); + return 0; +} + +/*! Smallest possible RPC declaration for test + * Yang/XML: + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element defined + * in [RFC6241]. + */ +static int +empty(clicon_handle h, /* Clicon handle */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ +{ + cprintf(cbret, ""); return 0; } @@ -190,39 +201,6 @@ plugin_statedata(clicon_handle h, return retval; } -/*! Plugin initialization. Create rpc callbacks - * plugin_init is called as soon as the plugin has been loaded and is - * assumed initialize the plugin's internal state if any as well as register - * any callbacks, configuration dependencies. - */ -int -plugin_init(clicon_handle h) -{ - int retval = -1; - - if (notification_timer_setup(h) < 0) - goto done; - /* Register callback for routing rpc calls */ - if (backend_rpc_cb_register(h, fib_route, - NULL, - "fib-route"/* Xml tag when callback is made */ - ) < 0) - goto done; - if (backend_rpc_cb_register(h, route_count, - NULL, - "route-count"/* Xml tag when callback is made */ - ) < 0) - goto done; - if (backend_rpc_cb_register(h, empty, - NULL, - "empty"/* Xml tag when callback is made */ - ) < 0) - goto done; - retval = 0; - done: - return retval; -} - /*! Plugin state reset. Add xml or set state in backend machine. * Called in each backend plugin. plugin_reset is called after all plugins * have been initialized. This give the application a chance to reset @@ -250,7 +228,7 @@ plugin_reset(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) + if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) goto done; retval = 0; done: @@ -278,3 +256,54 @@ plugin_start(clicon_handle h, { return 0; } + +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + NULL, /* exit */ + .ca_reset=plugin_reset, /* reset */ + .ca_statedata=plugin_statedata, /* statedata */ + .ca_trans_begin=NULL, /* trans begin */ + .ca_trans_validate=transaction_validate,/* trans validate */ + .ca_trans_complete=NULL, /* trans complete */ + .ca_trans_commit=transaction_commit, /* trans commit */ + .ca_trans_end=NULL, /* trans end */ + .ca_trans_abort=NULL /* trans abort */ +}; + +/*! Backend plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s backend", __FUNCTION__); + if (notification_timer_setup(h) < 0) + goto done; + /* Register callback for routing rpc calls */ + if (rpc_callback_register(h, fib_route, + NULL, + "fib-route"/* Xml tag when callback is made */ + ) < 0) + goto done; + if (rpc_callback_register(h, route_count, + NULL, + "route-count"/* Xml tag when callback is made */ + ) < 0) + goto done; + if (rpc_callback_register(h, empty, + NULL, + "empty"/* Xml tag when callback is made */ + ) < 0) + goto done; + /* Return plugin API */ + return &api; + done: + return NULL; +} + diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c new file mode 100644 index 00000000..d6ed4600 --- /dev/null +++ b/example/example_backend_nacm.c @@ -0,0 +1,119 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, indicate + your decision by deleting the provisions above and replace them with the + notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * + * IETF yang routing example + * Secondary backend for testing more than one backend plugin + */ + + +#include +#include +#include +#include +#include +#include +#include + +/* clicon */ +#include + +/* Clicon library functions. */ +#include + +/* These include signatures for plugin and transaction callbacks. */ +#include + + +/*! Called to get NACM state data + * @param[in] h Clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] xtop XML tree, on entry. + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get + * @note this example code returns a static statedata used in testing. + * Real code would poll state + */ +int +nacm_statedata(clicon_handle h, + char *xpath, + cxobj *xstate) +{ + int retval = -1; + cxobj **xvec = NULL; + + /* Example of (static) statedata, real code would poll state */ + if (xml_parse_string("" + "0" + "0" + "0" + "", NULL, &xstate) < 0) + goto done; + retval = 0; + done: + if (xvec) + free(xvec); + return retval; +} + +int +plugin_start(clicon_handle h, + int argc, + char **argv) +{ + return 0; +} + +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "nacm", /* name */ /*--- Common fields. ---*/ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + NULL, /* exit */ + .ca_reset=NULL, /* reset */ + .ca_statedata=nacm_statedata, /* statedata */ +}; + +/*! Backend plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s backend nacm", __FUNCTION__); + return &api; +} diff --git a/example/routing_cli.c b/example/example_cli.c similarity index 84% rename from example/routing_cli.c rename to example/example_cli.c index b054d943..b8a91fa3 100644 --- a/example/routing_cli.c +++ b/example/example_cli.c @@ -52,19 +52,6 @@ #include #include -/* - * Plugin initialization - */ -int -plugin_init(clicon_handle h) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - srandom(tv.tv_usec); - - return 0; -} /*! Example cli function */ int @@ -107,7 +94,7 @@ fib_route_rpc(clicon_handle h, /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", instance) < 0) + if (xml_parse_va(&xtop, NULL, "%s", cv_string_get(instance)) < 0) goto done; /* Skip top-level */ xrpc = xml_child_i(xtop, 0); @@ -125,3 +112,28 @@ fib_route_rpc(clicon_handle h, return retval; } +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + .ca_prompt=NULL, /* cli_prompthook_t */ + .ca_suspend=NULL, /* cligen_susp_cb_t */ + .ca_interrupt=NULL, /* cligen_interrupt_cb_t */ +}; + +/*! CLI plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + + return &api; +} diff --git a/example/routing_cli.cli b/example/example_cli.cli similarity index 96% rename from example/routing_cli.cli rename to example/example_cli.cli index 3c8a6a2d..6f8fdcfd 100644 --- a/example/routing_cli.cli +++ b/example/example_cli.cli @@ -1,7 +1,7 @@ # Common CLI syntax for both server and PMNode operatio mode -CLICON_MODE="routing"; +CLICON_MODE="example"; CLICON_PROMPT="%U@%H> "; -CLICON_PLUGIN="routing_cli"; +CLICON_PLUGIN="example_cli"; # Note, when switching to PT, change datamodel to only @datamodel set @datamodel:example, cli_set(); @@ -30,6 +30,7 @@ compare("Compare running and candidate"), compare_dbs((int32)1); show("Show a particular state of the system"){ xpath("Show configuration") ("XPATH expression"), show_conf_xpath("candidate"); + version("Show version"), cli_show_version("candidate", "text", "/"); compare("Compare candidate and running databases"), compare_dbs((int32)0);{ xml("Show comparison in xml"), compare_dbs((int32)0); text("Show comparison in text"), compare_dbs((int32)1); diff --git a/example/routing_netconf.c b/example/example_netconf.c similarity index 65% rename from example/routing_netconf.c rename to example/example_netconf.c index 6ee613fe..9993e851 100644 --- a/example/routing_netconf.c +++ b/example/example_netconf.c @@ -46,18 +46,7 @@ #include #include - -/* - * Plugin initialization - */ -int -plugin_init(clicon_handle h) -{ - return 0; -} - -/* - * Plugin start +/*! Plugin start * Called once everything has been initialized, right before * the main event loop is entered. */ @@ -73,3 +62,40 @@ plugin_exit(clicon_handle h) return 0; } +/*! Local example netconf rpc callback + */ +int netconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + clicon_debug(1, "%s restconf", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + +clixon_plugin_api * clixon_plugin_init(clicon_handle h); + +static struct clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + plugin_exit /* exit */ +}; + +/*! Netconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, netconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; + return &api; +} + diff --git a/example/example_restconf.c b/example/example_restconf.c new file mode 100644 index 00000000..2b22ce16 --- /dev/null +++ b/example/example_restconf.c @@ -0,0 +1,332 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include +#include + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* Use http basic auth. Set by starting restonf with: + clixon_restconf ... -- -a +*/ +static int basic_auth = 0; + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + @note what is copyright of this? + */ +int +b64_decode(const char *src, + char *target, + size_t targsize) +{ + int tarindex, state, ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = ((pos - Base64) & 0x0f) + << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = ((pos - Base64) & 0x03) + << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + default: + return -1; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} + +/*! Process a rest request that requires (cookie) "authentication" + * @param[in] h Clixon handle + * @param[in] arg Argument. Here: Fastcgi request handle + * @retval -1 Fatal error + * @retval 0 Unauth + * @retval 1 Auth + * @note: Three hardwired users: adm1, wilma, guest w password "bar". + * Enabled by passing -- -a to the main function + */ +int +example_restconf_credentials(clicon_handle h, + void *arg) +{ + int retval = -1; + FCGX_Request *r = (FCGX_Request *)arg; + cxobj *xt = NULL; + char *auth; + char *user = NULL; + char *passwd; + char *passwd2 = ""; + size_t authlen; + cbuf *cb = NULL; + int ret; + + /* HTTP basic authentication not enabled, pass with user "none" */ + if (basic_auth==0) + goto ok; + /* At this point in the code we must use HTTP basic authentication */ + if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL) + goto fail; + if (strlen(auth) < strlen("Basic ")) + goto fail; + if (strncmp("Basic ", auth, strlen("Basic "))) + goto fail; + auth += strlen("Basic "); + authlen = strlen(auth)*2; + if ((user = malloc(authlen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(user, 0, authlen); + if ((ret = b64_decode(auth, user, authlen)) < 0) + goto done; + /* auth string is on the format user:passwd */ + if ((passwd = index(user,':')) == NULL) + goto fail; + *passwd = '\0'; + passwd++; + clicon_debug(1, "%s http user:%s passwd:%s", __FUNCTION__, user, passwd); + /* Here get auth sub-tree whjere all the users are */ + if ((cb = cbuf_new()) == NULL) + goto done; + /* Hardcoded user/passwd */ + if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 || + strcmp(user, "quest")==0){ + passwd2 = "bar"; + } + if (strcmp(passwd, passwd2)) + goto fail; + retval = 1; + clicon_debug(1, "%s user:%s", __FUNCTION__, user); + if (clicon_username_set(h, user) < 0) + goto done; + ok: /* authenticated */ + retval = 1; + done: /* error */ + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (user) + free(user); + if (cb) + cbuf_free(cb); + if (xt) + xml_free(xt); + return retval; + fail: /* unauthenticated */ + retval = 0; + goto done; +} + +/*! Local example restconf rpc callback + */ +int +restconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + // FCGX_Request *r = (FCGX_Request *)arg; + clicon_debug(1, "%s", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + +/*! Start example restonf plugin. Set authentication method + * Arguments are argc/argv after -- + * Currently defined: -a enable http basic authentication + * Note hardwired users adm1, wilma and guest + */ +int +example_restconf_start(clicon_handle h, + int argc, + char **argv) +{ + char c; + + clicon_debug(1, "%s argc:%d", __FUNCTION__, argc); + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "a")) != -1){ + switch (c) { + case 'a': + basic_auth = 1; + break; + default: + break; + } + } + return 0; +} + +clixon_plugin_api * clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + example_restconf_start,/* start */ + NULL, /* exit */ + .ca_auth=example_restconf_credentials /* auth */ +}; + +/*! Restconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, restconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; + return &api; +} diff --git a/example/routing.conf.local b/example/routing.conf.local deleted file mode 100644 index a6caa01a..00000000 --- a/example/routing.conf.local +++ /dev/null @@ -1,17 +0,0 @@ -# Main YANG module first parsed by parser (in CLICON_YANG_DIR). eg clicon.yang. - -# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file -CLICON_CLI_MODE routing - -# Option used to construct initial yang file: -# [@] -#CLICON_YANG_MODULE_MAIN ietf-ip -CLICON_YANG_MODULE_MAIN example - -# Option used to construct initial yang file: -# [@] -#CLICON_YANG_MODULE_REVISION 2014-06-16 - -# Set to 0 if you want CLI to wrap to next line. -# Set to 1 if you want CLI to scroll sideways when approaching right margin -CLICON_CLI_LINESCROLLING 0 diff --git a/example/routing.xml b/example/routing.xml deleted file mode 100644 index 6ed38f96..00000000 --- a/example/routing.xml +++ /dev/null @@ -1,18 +0,0 @@ - - /usr/local/etc/routing.xml - /usr/local/share/routing/yang - example - routing - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - /usr/local/lib/routing/clispec - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - 1 - /usr/local/var/routing - /usr/local/lib/xmldb/text.so - 0 - init - diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index d356c165..5b0cb825 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -27,9 +27,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CRYPT_H -/* Define to 1 if you have the header file. */ -#undef HAVE_DEPOT_H - /* Define to 1 if you have the `inet_aton' function. */ #undef HAVE_INET_ATON @@ -54,9 +51,6 @@ /* Define to 1 if you have the `nsl' library (-lnsl). */ #undef HAVE_LIBNSL -/* Define to 1 if you have the `qdbm' library (-lqdbm). */ -#undef HAVE_LIBQDBM - /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET @@ -66,9 +60,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H -/* Define to 1 if you have the header file. */ -#undef HAVE_QDBM_DEPOT_H - /* Define to 1 if you have the `sigaction' function. */ #undef HAVE_SIGACTION diff --git a/include/clixon_config.h.in~ b/include/clixon_config.h.in~ new file mode 100644 index 00000000..d356c165 --- /dev/null +++ b/include/clixon_config.h.in~ @@ -0,0 +1,142 @@ +/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ + +/* Clixon data dir for system yang files etc */ +#undef CLIXON_DATADIR + +/* Location for apps to find default config file */ +#undef CLIXON_DEFAULT_CONFIG + +/* Clixon major release */ +#undef CLIXON_VERSION_MAJOR + +/* Clixon minor release */ +#undef CLIXON_VERSION_MINOR + +/* Clixon path version */ +#undef CLIXON_VERSION_PATCH + +/* Clixon version string */ +#undef CLIXON_VERSION_STRING + +/* Define to 1 if you have the `alphasort' function. */ +#undef HAVE_ALPHASORT + +/* Define to 1 if you have the header file. */ +#undef HAVE_CLIGEN_CLIGEN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_CRYPT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_DEPOT_H + +/* Define to 1 if you have the `inet_aton' function. */ +#undef HAVE_INET_ATON + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `cligen' library (-lcligen). */ +#undef HAVE_LIBCLIGEN + +/* Define to 1 if you have the `crypt' library (-lcrypt). */ +#undef HAVE_LIBCRYPT + +/* Define to 1 if you have the `dl' library (-ldl). */ +#undef HAVE_LIBDL + +/* Define to 1 if you have the `fcgi' library (-lfcgi). */ +#undef HAVE_LIBFCGI + +/* Define to 1 if you have the `m' library (-lm). */ +#undef HAVE_LIBM + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `qdbm' library (-lqdbm). */ +#undef HAVE_LIBQDBM + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_IF_VLAN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_QDBM_DEPOT_H + +/* Define to 1 if you have the `sigaction' function. */ +#undef HAVE_SIGACTION + +/* Define to 1 if you have the `sigvec' function. */ +#undef HAVE_SIGVEC + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strlcpy' function. */ +#undef HAVE_STRLCPY + +/* Define to 1 if you have the `strndup' function. */ +#undef HAVE_STRNDUP + +/* Define to 1 if you have the `strsep' function. */ +#undef HAVE_STRSEP + +/* Define to 1 if you have the `strverscmp' function. */ +#undef HAVE_STRVERSCMP + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UCRED_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `versionsort' function. */ +#undef HAVE_VERSIONSORT + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +#undef YYTEXT_POINTER + +#include diff --git a/include/clixon_config.h~ b/include/clixon_config.h~ new file mode 100644 index 00000000..5be37864 --- /dev/null +++ b/include/clixon_config.h~ @@ -0,0 +1,134 @@ +/* include/clixon_config.h. Generated from clixon_config.h.in by configure. */ +/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ + +/* Clixon major release */ +#define CLIXON_VERSION_MAJOR 3 + +/* Clixon minor release */ +#define CLIXON_VERSION_MINOR 3 + +/* Clixon path version */ +#define CLIXON_VERSION_PATCH 2 + +/* Clixon version string */ +#define CLIXON_VERSION_STRING "3.3.2" + +/* Define to 1 if you have the `alphasort' function. */ +#define HAVE_ALPHASORT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CLIGEN_CLIGEN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CRYPT_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DEPOT_H */ + +/* Define to 1 if you have the `inet_aton' function. */ +#define HAVE_INET_ATON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `crypt' library (-lcrypt). */ +#define HAVE_LIBCRYPT 1 + +/* Define to 1 if you have the `dl' library (-ldl). */ +#define HAVE_LIBDL 1 + +/* Define to 1 if you have the `fcgi' library (-lfcgi). */ +#define HAVE_LIBFCGI 1 + +/* Define to 1 if you have the `m' library (-lm). */ +#define HAVE_LIBM 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define to 1 if you have the `qdbm' library (-lqdbm). */ +#define HAVE_LIBQDBM 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_IF_VLAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_QDBM_DEPOT_H 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `sigvec' function. */ +#define HAVE_SIGVEC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef HAVE_STRLCPY */ + +/* Define to 1 if you have the `strndup' function. */ +#define HAVE_STRNDUP 1 + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strverscmp' function. */ +#define HAVE_STRVERSCMP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UCRED_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `versionsort' function. */ +#define HAVE_VERSIONSORT 1 + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +#define YYTEXT_POINTER 1 + +#include diff --git a/lib/clixon/Makefile.in b/lib/clixon/Makefile.in index 0cfe88e8..e455a860 100644 --- a/lib/clixon/Makefile.in +++ b/lib/clixon/Makefile.in @@ -44,8 +44,8 @@ depend: install: install-include: - install -m 755 -d $(DESTDIR)$(includedir)/clixon - install -m 644 *.h $(DESTDIR)$(includedir)/clixon + install -m 0755 -d $(DESTDIR)$(includedir)/clixon + install -m 0644 *.h $(DESTDIR)$(includedir)/clixon uninstall: rm -rf $(DESTDIR)$(includedir)/clixon diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 9422c501..05432227 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -83,6 +83,7 @@ #include #include #include +#include /* * Global variables generated by Makefile diff --git a/lib/clixon/clixon_handle.h b/lib/clixon/clixon_handle.h index d56b31dd..897560c3 100644 --- a/lib/clixon/clixon_handle.h +++ b/lib/clixon/clixon_handle.h @@ -50,6 +50,9 @@ typedef struct {float a;} *clicon_handle; typedef void *clicon_handle; #endif +/* The dynamicically loadable plugin object handle (should be in clixon_plugin.h) */ +typedef void *plghndl_t; + /* * Prototypes */ diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h new file mode 100644 index 00000000..f65bc9dc --- /dev/null +++ b/lib/clixon/clixon_netconf_lib.h @@ -0,0 +1,66 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Netconf library functions. See RFC6241 + * + */ +#ifndef _CLIXON_NETCONF_LIB_H +#define _CLIXON_NETCONF_LIB_H + +/* + * Prototypes + */ +int netconf_in_use(cbuf *cb, char *type, char *message); +int netconf_invalid_value(cbuf *cb, char *type, char *message); +int netconf_too_big(cbuf *cb, char *type, 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_unknown_attribute(cbuf *cb, char *type, char *info, char *message); +int netconf_missing_element(cbuf *cb, char *type, char *info, char *message); +int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_namespace(cbuf *cb, char *type, char *info, 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_lock_denied(cbuf *cb, char *info, char *message); +int netconf_resource_denied(cbuf *cb, char *type, char *message); +int netconf_rollback_failed(cbuf *cb, char *type, char *message); +int netconf_data_exists(cbuf *cb, char *message); +int netconf_data_missing(cbuf *cb, char *message); +int netconf_operation_not_supported(cbuf *cb, char *type, char *message); +int netconf_operation_failed(cbuf *cb, char *type, char *message); +int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); +int netconf_malformed_message(cbuf *cb, char *message); +int netconf_malformed_message_xml(cxobj **xret, char *message); + +#endif /* _CLIXON_NETCONF_LIB_H */ diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 6727c157..1f95c73a 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -43,23 +43,26 @@ */ /* default group membership to access config unix socket */ #define CLICON_SOCK_GROUP "clicon" -/* Default name of master plugin */ -#define CLICON_MASTER_PLUGIN "master" /* * Types */ /*! Controls how keywords a generated in CLI syntax / prints from object model - * Example syntax a.b[] $!x $y: - * NONE: a b ; - * VARS: a b y ; - * ALL: a b x y ; + * Example YANG: + * list a { + * key x; + * leaf x; + * leaf y; + * } + * NONE: a ; + * VARS: a y ; + * ALL: a x y ; */ enum genmodel_type{ GT_ERR =-1, /* Error */ GT_NONE=0, /* No extra keywords */ - GT_VARS, /* Keywords on non-index variables */ + GT_VARS, /* Keywords on non-key variables */ GT_ALL, /* Keywords on all variables */ }; @@ -137,9 +140,6 @@ static inline char *clicon_sock_group(clicon_handle h){ static inline char *clicon_backend_pidfile(clicon_handle h){ return clicon_option_str(h, "CLICON_BACKEND_PIDFILE"); } -static inline char *clicon_master_plugin(clicon_handle h){ - return clicon_option_str(h, "CLICON_MASTER_PLUGIN"); -} static inline char *clicon_xmldb_dir(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_DIR"); } @@ -179,4 +179,9 @@ int clicon_xmldb_api_set(clicon_handle h, void *xa_api); void *clicon_xmldb_handle_get(clicon_handle h); int clicon_xmldb_handle_set(clicon_handle h, void *xh); +/**/ +/* Set and get authorized user name */ +char *clicon_username_get(clicon_handle h); +int clicon_username_set(clicon_handle h, void *username); + #endif /* _CLIXON_OPTIONS_H_ */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 22ef53b5..f7aa7174 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -38,14 +38,32 @@ #ifndef _CLIXON_PLUGIN_H_ #define _CLIXON_PLUGIN_H_ +/* + * Constants + */ +/* Hardcoded plugin symbol. Must exist in all plugins to kickstart */ +#define CLIXON_PLUGIN_INIT "clixon_plugin_init" + /* * Types */ -/* The dynamicically loadable plugin object handle */ +/* Dynamicically loadable plugin object handle. @see return value of dlopen(3) */ typedef void *plghndl_t; -/* Find plugin by name callback. XXX Should be clicon internal */ -typedef void *(find_plugin_t)(clicon_handle, char *); +/*! Registered RPC callback function + * @param[in] h Clicon handle + * @param[in] xn Request: + * @param[out] cbret Return xml tree, eg ..., = 3 +int xml_parse_va(cxobj **xt, yang_spec *yspec, const char *format, ...) __attribute__ ((format (printf, 3, 4))); +#else int xml_parse_va(cxobj **xt, yang_spec *yspec, const char *format, ...); - +#endif int xmltree2cbuf(cbuf *cb, cxobj *x, int level); int xml_copy_one(cxobj *xn0, cxobj *xn1); int xml_copy(cxobj *x0, cxobj *x1); diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 49b192f8..58607396 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -78,7 +78,7 @@ typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value); typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop); /* Type of xmldb put function */ -typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt); +typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); /* Type of xmldb copy function */ typedef int (xmldb_copy_t)(xmldb_handle xh, const char *from, const char *to); @@ -139,7 +139,7 @@ int xmldb_disconnect(clicon_handle h); int xmldb_getopt(clicon_handle h, char *optname, void **value); int xmldb_setopt(clicon_handle h, char *optname, void *value); int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt); +int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); int xmldb_copy(clicon_handle h, const char *from, const char *to); int xmldb_lock(clicon_handle h, const char *db, int pid); int xmldb_unlock(clicon_handle h, const char *db); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6d3befc3..6a5a32fe 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -64,8 +64,8 @@ int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, - int schemanode, cxobj **xpathp, yang_node **ypathp); -int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); + yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); +int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 084fe3c5..c77d64aa 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -31,6 +31,9 @@ ***** END LICENSE BLOCK ***** + * Yang functions + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ #ifndef _CLIXON_YANG_H_ @@ -114,6 +117,7 @@ enum rfc_6020{ Y_TYPEDEF, Y_UNIQUE, Y_UNITS, + Y_UNKNOWN, Y_USES, Y_VALUE, Y_WHEN, @@ -122,14 +126,45 @@ enum rfc_6020{ Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ }; +/* Type used to group yang nodes used in some functions + * See RFC7950 Sec 3 + */ +enum yang_class{ + YC_NONE, /* Someting else,... */ + YC_DATANODE, /* See yang_datanode() */ + YC_DATADEFINITION, /* See yang_datadefinition() */ + YC_SCHEMANODE /* See yang_schemanode() */ +}; +typedef enum yang_class yang_class; + #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ -/* Yang data node */ +/* Yang data node + * See RFC7950 Sec 3: + * o data node: A node in the schema tree that can be instantiated in a + * data tree. One of container, leaf, leaf-list, list, anydata, and + * anyxml. + */ #define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML) -/* Yang schema node */ +/* Yang data definition statement + * See RFC 7950 Sec 3: + * o data definition statement: A statement that defines new data + * nodes. One of "container", "leaf", "leaf-list", "list", "choice", + * "case", "augment", "uses", "anydata", and "anyxml". + */ +#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES) + + +/* Yang schema node . + * See RFC 7950 Sec 3: + * o schema node: A node in the schema tree. One of action, container, + * leaf, leaf-list, list, choice, case, rpc, input, output, + * notification, anydata, and anyxml. + */ #define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION) + typedef struct yang_stmt yang_stmt; /* forward */ /*! Yang type cache. Yang type statements can cache all typedef info here @@ -161,7 +196,9 @@ struct yang_stmt{ leaf-list, config: boolean true or false mandatory: boolean true or false - fraction-digits for fraction-digits */ + fraction-digits for fraction-digits + unkown-stmt (argument) + */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max Y_LIST: vector of keys @@ -206,15 +243,15 @@ yang_stmt *ys_dup(yang_stmt *old); int yn_insert(yang_node *yn_parent, yang_stmt *ys_child); yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); -char *ytype_prefix(yang_stmt *ys); -char *ytype_id(yang_stmt *ys); +char *yarg_prefix(yang_stmt *ys); +char *yarg_id(yang_stmt *ys); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); +yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); @@ -223,11 +260,11 @@ int yang_parse(clicon_handle h, const char *yang_dir, int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, - yang_stmt **yres); + enum rfc_6020 keyword, yang_stmt **yres); int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, - yang_stmt **yres); + enum rfc_6020 keyword, yang_stmt **yres); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); -int ys_parse_sub(yang_stmt *ys); +int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); yang_spec *yang_spec_netconf(clicon_handle h); diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 291766cf..61b581af 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -68,7 +68,6 @@ char *cv2yang_type(enum cv_type cv_type); yang_stmt *yang_find_identity(yang_stmt *ys, char *identity); int ys_cv_validate(cg_var *cv, yang_stmt *ys, char **reason); int clicon_type2cv(char *type, char *rtype, enum cv_type *cvtype); -char *ytype_id(yang_stmt *ys); int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype, int *options, cg_var **mincv, cg_var **maxcv, char **pattern, uint8_t *fraction_digits); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 77d08d90..b26efee5 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -51,7 +51,8 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ VPATH = @srcdir@ CC = @CC@ -CFLAGS = -fPIC @CFLAGS@ +CFLAGS = -fPIC @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ @@ -68,7 +69,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_json.c clixon_yang.c clixon_yang_type.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xsl.c clixon_sha1.c clixon_xml_db.c + clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_netconf_lib.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ @@ -181,8 +182,8 @@ install: install-lib install-include: install-lib: $(MYLIB) - install -m 755 -d $(DESTDIR)$(libdir) - install -m 755 $(MYLIB) $(DESTDIR)$(libdir) + install -m 0755 -d $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon.so.3 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon.so diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index a1a552bd..12df1b83 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -62,8 +62,7 @@ #include "clixon_string.h" #include "clixon_file.h" -/* - * qsort function +/*! qsort "compar" for directory alphabetically sorting, see qsort(3) */ static int clicon_file_dirent_sort(const void* arg1, @@ -79,8 +78,7 @@ clicon_file_dirent_sort(const void* arg1, #endif /* HAVE_STRVERSCMP */ } - -/*! Return sorted matching files from a directory +/*! Return alphabetically sorted files from a directory matching regexp * @param[in] dir Directory path * @param[out] ent Entries pointer, will be filled in with dir entries. Free * after use @@ -120,27 +118,23 @@ clicon_file_dirent(const char *dir, struct dirent *new = NULL; struct dirent *dvecp = NULL; - *ent = NULL; nent = 0; - if (regexp && (res = regcomp(&re, regexp, REG_EXTENDED)) != 0) { regerror(res, &re, errbuf, sizeof(errbuf)); clicon_err(OE_DB, 0, "regcomp: %s", errbuf); return -1; } - - if ((dirp = opendir (dir)) == NULL) { + if ((dirp = opendir(dir)) == NULL) { if (errno == ENOENT) /* Dir does not exist -> return 0 matches */ retval = 0; else clicon_err(OE_UNIX, errno, "opendir(%s)", dir); goto quit; } - - for (res = readdir_r (dirp, &dent, &dresp); + for (res = readdir_r(dirp, &dent, &dresp); dresp; - res = readdir_r (dirp, &dent, &dresp)) { + res = readdir_r(dirp, &dent, &dresp)) { if (res != 0) { clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno)); goto quit; @@ -162,13 +156,12 @@ clicon_file_dirent(const char *dir, if ((type & st.st_mode) == 0) continue; } - if ((tmp = realloc(new, (nent+1)*sizeof(*dvecp))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); goto quit; } new = tmp; - memcpy (&new[nent], &dent, sizeof(dent)); + memcpy(&new[nent], &dent, sizeof(dent)); nent++; } /* while */ diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index beff1c41..edac1382 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -50,9 +50,9 @@ #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_log.h" #include "clixon_err.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #define CLICON_MAGIC 0x99aafabe @@ -61,15 +61,19 @@ /*! Internal structure of basic handle. Also header of all other handles. * @note If you change here, you must also change the structs below: - * @see struct cli_handle, struct backend_handle + * @see struct cli_handle + * @see struct backend_handle */ struct clicon_handle { - int ch_magic; /* magic (HDR) */ - clicon_hash_t *ch_copt; /* clicon option list (HDR) */ - clicon_hash_t *ch_data; /* internal clicon data (HDR) */ + int ch_magic; /* magic (HDR) */ + clicon_hash_t *ch_copt; /* clicon option list (HDR) */ + clicon_hash_t *ch_data; /* internal clicon data (HDR) */ }; /*! Internal call to allocate a CLICON handle. + * + * @param[in] size Size of handle (internal) struct. + * @retval h Clicon handle * * There may be different variants of handles with some common options. * So far the only common options is a MAGIC cookie for sanity checks and @@ -102,6 +106,7 @@ clicon_handle_init0(int size) /*! Basic CLICON init functions returning a handle for API access. * + * @retval h Clicon handle * This is the first call to CLICON basic API which returns a handle to be * used in the API functions. There are other clicon_init functions for more * elaborate applications (cli/backend/netconf). This should be used by the most @@ -114,6 +119,7 @@ clicon_handle_init(void) } /*! Deallocate clicon handle, including freeing handle data. + * @param[in] h Clicon handle * @Note: handle 'h' cannot be used in calls after this */ int @@ -131,9 +137,10 @@ clicon_handle_exit(clicon_handle h) return 0; } -/* - * Check struct magic number for sanity checks - * return 0 if OK, -1 if fail. +/*! Check struct magic number for sanity checks + * @param[in] h Clicon handle + * @retval 0 Sanity check OK + * @retval -1 Sanity check failed */ int clicon_handle_check(clicon_handle h) @@ -144,8 +151,8 @@ clicon_handle_check(clicon_handle h) return ch->ch_magic == CLICON_MAGIC ? 0 : -1; } -/* - * Return clicon options (hash-array) given a handle. +/*! Return clicon options (hash-array) given a handle. + * @param[in] h Clicon handle */ clicon_hash_t * clicon_options(clicon_handle h) @@ -155,8 +162,8 @@ clicon_options(clicon_handle h) return ch->ch_copt; } -/* - * Return clicon data (hash-array) given a handle. +/*! Return clicon data (hash-array) given a handle. + * @param[in] h Clicon handle */ clicon_hash_t * clicon_data(clicon_handle h) @@ -165,4 +172,3 @@ clicon_data(clicon_handle h) return ch->ch_data; } - diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 3e225396..b14592cc 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -216,6 +216,9 @@ json_str_escape(char *str) for (i=0;i[ \t] \n { _JY->jy_linenum++; } +\r { } <> { return J_EOF; } \{ { return *yytext; } \} { return *yytext; } diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c new file mode 100644 index 00000000..a7cf2f9e --- /dev/null +++ b/lib/src/clixon_netconf_lib.c @@ -0,0 +1,936 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + + * Netconf library functions. See RFC6241 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_string.h" +#include "clixon_err.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_log.h" +#include "clixon_xml.h" +#include "clixon_netconf_lib.h" + +/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A + * + * The request requires a resource that already is in use. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_in_use(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "in-use" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A + * + * The request specifies an unacceptable value for one or more parameters. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_invalid_value(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "invalid-value" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf too-big error XML tree according to RFC 6241 Appendix A + * + * The request or response (that would be generated) is + * too large for the implementation to handle. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "transport", "rpc", "application", "protocol" + * @param[in] message Error message + */ +int +netconf_too_big(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "too-big" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A + * + * An expected attribute is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "missing-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A + * + * An attribute value is not correct; e.g., wrong type, + * out of range, pattern mismatch. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_bad_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "bad-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknwon-attribute error XML tree according to RFC 6241 App A + * + * An unexpected attribute is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_unknown_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "unknown-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf missing-element error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "missing-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf bad-element error XML tree according to RFC 6241 App A + * + * An element value is not correct; e.g., wrong type, out of range, + * pattern mismatch. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_bad_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "bad-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A + * + * An unexpected element is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_unknown_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "unknown-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A + * + * An unexpected namespace is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element or bad-namespace xml + * @param[in] message Error message + */ +int +netconf_unknown_namespace(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "unknown-namespace" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf access-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_access_denied(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "access-denied" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf access-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] xret Error XML tree + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_access_denied_xml(cxobj **xret, + char *type, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "access-denied" + "%s" + "error", type) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Create Netconf lock-denied error XML tree according to RFC 6241 App A + * + * Access to the requested lock is denied because the lock is currently held + * by another entity. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] info session-id xml + * @param[in] message Error message + */ +int +netconf_lock_denied(cbuf *cb, + char *info, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "lock-denied" + "protocol" + "%s" + "error", + info) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf resource-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "transport, "rpc", "application", "protocol" + * @param[in] message Error message + */ +int +netconf_resource_denied(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "resource-denied" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf rollback-failed error XML tree according to RFC 6241 App A + * + * Request to roll back some configuration change (via rollback-on-error or + * operations) was not completed for some reason. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_rollback_failed(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "rollback-failed" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf data-exists error XML tree according to RFC 6241 Appendix A + * + * Request could not be completed because the relevant + * data model content already exists. For example, + * a "create" operation was attempted on data that already exists. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + */ +int +netconf_data_exists(cbuf *cb, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "data-exists" + "application" + "error") <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf data-missing error XML tree according to RFC 6241 App A + * + * Request could not be completed because the relevant data model content + * does not exist. For example, a "delete" operation was attempted on + * data that does not exist. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + */ +int +netconf_data_missing(cbuf *cb, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "data-missing" + "application" + "error") <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf operation-not-supported error XML according to RFC 6241 App A + * + * Request could not be completed because the requested operation is not + * supported by this implementation. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_not_supported(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "operation-not-supported" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A + * + * Request could not be completed because the requested operation failed for + * some reason not covered by any other error condition. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_failed(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "operation-failed" + "%s" + "error", + type) <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") < 0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A + * + * Request could not be completed because the requested operation failed for + * some reason not covered by any other error condition. + * @param[out] xret Error XML tree + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_failed_xml(cxobj **xret, + char *type, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "operation-failed" + "%s" + "error", type) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Create Netconf malformed-message error XML tree according to RFC 6241 App A + * + * A message could not be handled because it failed to be parsed correctly. + * For example, the message is not well-formed XML or it uses an + * invalid character set. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + * @note New in :base:1.1 + */ +int +netconf_malformed_message(cbuf *cb, + char *message) +{ + int retval = -1; + char *encstr = NULL; + + if (cprintf(cb, "" + "malformed-message" + "rpc" + "error") <0) + goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + if (encstr) + free(encstr); + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf malformed-message error XML tree according to RFC 6241 App A + * + * A message could not be handled because it failed to be parsed correctly. + * For example, the message is not well-formed XML or it uses an + * invalid character set. + * @param[out] xret Error XML tree + * @param[in] message Error message + * @note New in :base:1.1 + */ +int +netconf_malformed_message_xml(cxobj **xret, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "malformed-message" + "rpc" + "error") < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 6c528662..45c642e0 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -64,9 +64,9 @@ #include "clixon_handle.h" #include "clixon_log.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_xml_map.h" @@ -209,7 +209,7 @@ clicon_options_main(clicon_handle h) /* If file ends with .xml, assume it is new format */ if ((suffix = rindex(configfile, '.')) != NULL){ suffix++; - xml = strcmp(suffix,"xml") == 0; + xml = strcmp(suffix, "xml") == 0; } if (xml){ /* Read clixon yang file */ if ((yspec = yspec_new()) == NULL) @@ -715,3 +715,32 @@ clicon_xmldb_handle_set(clicon_handle h, } +/*! Get authorized user name + * @param[in] h Clicon handle + * @retval xh XMLDB storage handle. If not connected return NULL + */ +char * +clicon_username_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + + return (char*)hash_value(cdat, "username", NULL); +} + +/*! Set authorized user name + * @param[in] h Clicon handle + * @param[in] xh XMLDB storage handle. If NULL reset it + * @note Just keep note of it, dont allocate it or so. + */ +int +clicon_username_set(clicon_handle h, + void *username) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (username == NULL) + return hash_del(cdat, "username"); + return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0; +} + + diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 39e78e7f..c8ce70e7 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -38,123 +38,467 @@ #include #include +#include #include #include #include +#include + +#include +#include + +/* cligen */ +#include #include "clixon_err.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_log.h" +#include "clixon_file.h" #include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" #include "clixon_plugin.h" +/* List of plugins XXX + * 1. Place in clixon handle not global variables + * 2. Use qelem circular lists + */ +static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */ +static int _clixon_nplugins = 0; /* Number of plugins */ -static find_plugin_t * -clicon_find_plugin(clicon_handle h) +/*! Iterator over clixon plugins + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] h Clicon handle + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = clixon_plugin_each(h, cp)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list + */ +clixon_plugin * +clixon_plugin_each(clicon_handle h, + clixon_plugin *cpprev) { - void *p; - find_plugin_t *fp = NULL; - clicon_hash_t *data = clicon_data(h); - - if ((p = hash_value(data, "CLICON_FIND_PLUGIN", NULL)) != NULL) - memcpy(&fp, p, sizeof(fp)); + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; - return fp; + if (cpprev == NULL) + cpnext = _clixon_plugins; + else{ + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; + } + if (cp && i < _clixon_nplugins-1) + cpnext = &_clixon_plugins[i+1]; + } + return cpnext; } - -/*! Return a function pointer based on name of plugin and function. - * If plugin is specified, ask daemon registered function to return - * the dlsym handle of the plugin. +/*! Reverse iterator over clixon plugins, iterater from nr to 0 + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] h Clicon handle + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list */ -void * -clicon_find_func(clicon_handle h, char *plugin, char *func) +clixon_plugin * +clixon_plugin_each_revert(clicon_handle h, + clixon_plugin *cpprev, + int nr) { - find_plugin_t *plgget; - void *dlhandle = NULL; + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; - if (plugin) { - /* find clicon_plugin_get() in global namespace */ - if ((plgget = clicon_find_plugin(h)) == NULL) { - clicon_err(OE_UNIX, errno, "Specified plugin not supported"); - return NULL; + if (cpprev == NULL) + cpnext = &_clixon_plugins[nr-1]; + else{ + for (i = nr-1; i >= 0; i--) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; } - dlhandle = plgget(h, plugin); + if (cp && i > 0) + cpnext = &_clixon_plugins[i-1]; } - - return dlsym(dlhandle, func); + return cpnext; +} + +/*! Find plugin by name + * @param[in] h Clicon handle + * @param[in] name Plugin name + * @retval p Plugin if found + * @retval NULL Not found + */ +clixon_plugin * +clixon_plugin_find(clicon_handle h, + char *name) +{ + int i; + clixon_plugin *cp = NULL; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if (strcmp(cp->cp_name, name) == 0) + return cp; + } + return NULL; } /*! Load a dynamic plugin object and call its init-function - * Note 'file' may be destructively modified * @param[in] h Clicon handle * @param[in] file Which plugin to load + * @param[in] function Which function symbol to load and call * @param[in] dlflags See man(3) dlopen + * @retval cp Clixon plugin structure + * @retval NULL Error + * @see clixon_plugins_load Load all plugins */ -plghndl_t -plugin_load(clicon_handle h, - char *file, - int dlflags) +static clixon_plugin * +plugin_load_one(clicon_handle h, + char *file, + char *function, + int dlflags) { - char *error; - void *handle = NULL; - plginit_t *initfn; + char *error; + void *handle = NULL; + plginit2_t *initfn; + clixon_plugin_api *api = NULL; + clixon_plugin *cp = NULL; + char *name; + char *p; clicon_debug(1, "%s", __FUNCTION__); dlerror(); /* Clear any existing error */ - if ((handle = dlopen (file, dlflags)) == NULL) { + if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); goto done; } - /* call plugin_init() if defined */ - if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){ - clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading restconf plugin %s", file); + /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */ + if ((initfn = dlsym(handle, function)) == NULL){ + clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file); goto err; } if ((error = (char*)dlerror()) != NULL) { clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); goto done; } - if (initfn(h) != 0) { + if ((api = initfn(h)) == NULL) { clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error", file); goto err; } + /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ + if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(cp, 0, sizeof(struct clixon_plugin)); + cp->cp_handle = handle; + /* Extract string after last '/' in filename, if any */ + name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; + /* strip extension, eg .so from name */ + if ((p=strrchr(name, '.')) != NULL) + *p = '\0'; + /* Copy name to struct */ + memcpy(cp->cp_name, name, strlen(name)+1); + + snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", + (int)strlen(name), name); + cp->cp_api = *api; + clicon_debug(1, "%s", __FUNCTION__); done: - return handle; + return cp; err: if (handle) dlclose(handle); return NULL; } - -/*! Unload a plugin - * @param[in] h Clicon handle - * @param[in] handle Clicon handle +/*! Load a set of plugin objects from a directory and and call their init-function + * @param[in] h Clicon handle + * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT) + * @param[in] dir Directory. .so files in this dir will be loaded. + * @param[in] regexp Regexp for matching files in plugin directory. Default *.so. + * @retval 0 OK + * @retval -1 Error */ int -plugin_unload(clicon_handle h, - plghndl_t *handle) +clixon_plugins_load(clicon_handle h, + char *function, + char *dir, + char *regexp) { - int retval = 0; - char *error; - plgexit_t *exitfn; + int retval = -1; + int ndp; + struct dirent *dp = NULL; + int i; + char filename[MAXPATHLEN]; + clixon_plugin *cp; - /* Call exit function is it exists */ - exitfn = dlsym(handle, PLUGIN_EXIT); - if (dlerror() == NULL) - exitfn(h); + clicon_debug(1, "%s", __FUNCTION__); + /* Get plugin objects names from plugin directory */ + if((ndp = clicon_file_dirent(dir, &dp, + regexp?regexp:"(.so)$", S_IFREG))<0) + goto done; + /* Load all plugins */ + for (i = 0; i < ndp; i++) { + snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); + clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", + (int)strlen(filename), filename); + if ((cp = plugin_load_one(h, filename, function, RTLD_NOW)) == NULL) + goto done; + _clixon_nplugins++; + if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) { + clicon_err(OE_UNIX, errno, "realloc"); + goto done; + } + _clixon_plugins[_clixon_nplugins-1] = *cp; + free(cp); + } + retval = 0; +done: + if (dp) + free(dp); + return retval; +} - dlerror(); /* Clear any existing error */ - if (dlclose(handle) != 0) { - error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); - /* Just report */ +/*! Call plugin_start in all plugins + * @param[in] h Clicon handle + * @param[in] argc + * @param[in] argv + * Call plugin start functions (if defined) with argc/argv multiple + * arguments. + * Typically the argc/argv are the ones appearing after "--", eg + * clicon_cli -f /etc/clicon.xml -- -a myopt + * In the example above argc=3 and + * argv[0]: clicon_cli + * argv[1]: -a + * argv[2]: myopt + */ +int +clixon_plugin_start(clicon_handle h, + int argc, + char **argv) +{ + clixon_plugin *cp; + int i; + plgstart_t *startfn; /* Plugin start */ + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((startfn = cp->cp_api.ca_start) == NULL) + continue; + // optind = 0; + if (startfn(h, argc, argv) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + } + return 0; +} + +/*! Unload all plugins: call exit function and close shared handle + * @param[in] h Clicon handle + */ +int +clixon_plugin_exit(clicon_handle h) +{ + clixon_plugin *cp; + plgexit_t *exitfn; + int i; + char *error; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((exitfn = cp->cp_api.ca_exit) == NULL) + continue; + if (exitfn(h) < 0) { + clicon_debug(1, "plugin_exit() failed\n"); + return -1; + } + if (dlclose(cp->cp_handle) != 0) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + } + } + if (_clixon_plugins){ + free(_clixon_plugins); + _clixon_plugins = NULL; + } + _clixon_nplugins = 0; + return 0; +} + + +/*! Run the restconf user-defined credentials callback if present + * Find first authentication callback and call that, then return. + * The callback is to set the authenticated user + * @param[in] h Clicon handle + * @param[in] arg Argument, such as fastcgi handler for restconf + * @retval -1 Error + * @retval 0 Not authenticated + * @retval 1 Authenticated + * @note If authenticated either a callback was called and clicon_username_set() + * Or no callback was found. + */ +int +clixon_plugin_auth(clicon_handle h, + void *arg) +{ + clixon_plugin *cp; + int i; + plgauth_t *authfn; /* Plugin auth */ + int retval = 1; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((authfn = cp->cp_api.ca_auth) == NULL) + continue; + if ((retval = authfn(h, arg)) < 0) { + clicon_debug(1, "plugin_auth() failed\n"); + return -1; + } + break; } return retval; } + +/*-------------------------------------------------------------------- + * RPC callbacks for both client/frontend and backend plugins. + * RPC callbacks are explicitly registered in the plugin_init() function + * with a tag and a function + * WHen the the tag is encountered, the callback is called. + * Primarily backend, but also netconf and restconf frontend plugins. + * CLI frontend so far have direct callbacks, ie functions in the cligen + * specification are directly dlsym:ed to the CLI plugin. + * It would be possible to use this rpc registering API for CLI plugins as well. + * + * @note may have a problem if callbacks are of different types + */ +typedef struct { + qelem_t rc_qelem; /* List header */ + clicon_rpc_cb rc_callback; /* RPC Callback */ + void *rc_arg; /* Application specific argument to cb */ + char *rc_tag; /* Xml/json tag when matched, callback called */ +} rpc_callback_t; + +/* List of rpc callback entries */ +static rpc_callback_t *rpc_cb_list = NULL; + +/*! Register a RPC callback + * Called from plugin to register a callback for a specific netconf XML tag. + * + * @param[in] h clicon handle + * @param[in] cb, Callback called + * @param[in] arg, Domain-specific argument to send to callback + * @param[in] tag Xml tag when callback is made + * @see rpc_callback_call + */ +int +rpc_callback_register(clicon_handle h, + clicon_rpc_cb cb, + void *arg, + char *tag) +{ + rpc_callback_t *rc; + + if ((rc = malloc(sizeof(rpc_callback_t))) == NULL) { + clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); + goto done; + } + memset (rc, 0, sizeof (*rc)); + rc->rc_callback = cb; + rc->rc_arg = arg; + rc->rc_tag = strdup(tag); /* XXX strdup memleak */ + INSQ(rc, rpc_cb_list); + return 0; + done: + if (rc){ + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return -1; +} + +/*! Delete all RPC callbacks + */ +int +rpc_callback_delete_all(void) +{ + rpc_callback_t *rc; + + while((rc = rpc_cb_list) != NULL) { + DELQ(rc, rpc_cb_list, rpc_callback_t *); + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return 0; +} + +/*! Search RPC callbacks and invoke if XML match with tag + * + * @param[in] h clicon handle + * @param[in] xn Sub-tree (under xorig) at child of rpc: . + * @param[out] xret Return XML, error or OK + * @param[in] arg Domain-speific arg (eg client_entry) + * + * @retval -1 Error + * @retval 0 OK, not found handler. + * @retval 1 OK, handler called + */ +int +rpc_callback_call(clicon_handle h, + cxobj *xe, + cbuf *cbret, + void *arg) +{ + int retval = -1; + rpc_callback_t *rc; + + if (rpc_cb_list == NULL) + return 0; + rc = rpc_cb_list; + do { + if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ + if ((retval = rc->rc_callback(h, xe, cbret, arg, rc->rc_arg)) < 0){ + clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); + goto done; + } + else{ + retval = 1; /* handled */ + goto done; + } + } + rc = NEXTQ(rpc_callback_t *, rc); + } while (rc != rpc_cb_list); + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; +} diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 4a5808cf..7c1bec04 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -60,9 +61,9 @@ #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_proto.h" #include "clixon_err.h" @@ -90,6 +91,7 @@ clicon_rpc_msg(clicon_handle h, cxobj *xret = NULL; yang_spec *yspec; + clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body); if ((sock = clicon_sock(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); goto done; @@ -200,7 +202,7 @@ clicon_rpc_netconf_xml(clicon_handle h, return retval; } -/*! Generate clicon error function call from Netconf error message +/*! Generate and log clicon error function call from Netconf error message * @param[in] xerr Netconf error message on the level: */ int @@ -264,10 +266,14 @@ clicon_rpc_get_config(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xd; - + char *username; + if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); + cprintf(cb, "<%s/>", db); if (xpath && strlen(xpath)) cprintf(cb, "", xpath); cprintf(cb, ""); @@ -303,7 +309,7 @@ clicon_rpc_get_config(clicon_handle h, * @param[in] op Operation on database item: OP_MERGE, OP_REPLACE * @param[in] xml XML string. Ex: ..... * @retval 0 OK - * @retval -1 Error + * @retval -1 Error and logged to syslog * @note xml arg need to have as top element * @code * if (clicon_rpc_edit_config(h, "running", OP_MERGE, @@ -322,10 +328,14 @@ clicon_rpc_edit_config(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); + cprintf(cb, "<%s/>", db); cprintf(cb, "%s", xml_operation2str(op)); if (xmlstr) @@ -356,6 +366,8 @@ clicon_rpc_edit_config(clicon_handle h, * @param[in] h CLICON handle * @param[in] db1 src database, eg "running" * @param[in] db2 dst database, eg "startup" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_copy_config(h, "running", "startup") < 0) * err; @@ -370,8 +382,12 @@ clicon_rpc_copy_config(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/><%s/>", db1, db2)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/><%s/>", + username?username:"", + db1, db2)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -391,6 +407,8 @@ clicon_rpc_copy_config(clicon_handle h, /*! Send a request to backend to delete a config database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_delete_config(h, "startup") < 0) * err; @@ -404,8 +422,11 @@ clicon_rpc_delete_config(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", + username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -425,6 +446,8 @@ clicon_rpc_delete_config(clicon_handle h, /*! Lock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_lock(clicon_handle h, @@ -434,8 +457,11 @@ clicon_rpc_lock(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", + username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -455,6 +481,8 @@ clicon_rpc_lock(clicon_handle h, /*! Unlock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_unlock(clicon_handle h, @@ -464,8 +492,10 @@ clicon_rpc_unlock(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -512,10 +542,14 @@ clicon_rpc_get(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xd; + char *username; if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, ""); + cprintf(cb, ""); if (xpath && strlen(xpath)) cprintf(cb, "", xpath); cprintf(cb, ""); @@ -547,6 +581,8 @@ clicon_rpc_get(clicon_handle h, /*! Close a (user) session * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_close_session(clicon_handle h) @@ -555,8 +591,11 @@ clicon_rpc_close_session(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", + username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -576,6 +615,8 @@ clicon_rpc_close_session(clicon_handle h) /*! Kill other user sessions * @param[in] h CLICON handle * @param[in] session_id Session id of other user session + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_kill_session(clicon_handle h, @@ -585,8 +626,11 @@ clicon_rpc_kill_session(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("%d", session_id)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("%d", + username?username:"", session_id)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -606,7 +650,8 @@ clicon_rpc_kill_session(clicon_handle h, /*! Send validate request to backend daemon * @param[in] h CLICON handle * @param[in] db Name of database - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_validate(clicon_handle h, @@ -616,8 +661,10 @@ clicon_rpc_validate(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -636,7 +683,8 @@ clicon_rpc_validate(clicon_handle h, /*! Commit changes send a commit request to backend daemon * @param[in] h CLICON handle - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_commit(clicon_handle h) @@ -645,8 +693,10 @@ clicon_rpc_commit(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -664,8 +714,9 @@ clicon_rpc_commit(clicon_handle h) } /*! Discard all changes in candidate / revert to running - * @param[in] h CLICON handle - * @retval 0 OK + * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_discard_changes(clicon_handle h) @@ -674,8 +725,10 @@ clicon_rpc_discard_changes(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -697,6 +750,9 @@ clicon_rpc_discard_changes(clicon_handle h) * @param{in] stream name of notificatio/log stream (CLICON is predefined) * @param{in] filter message filter, eg xpath for xml notifications * @param[out] s0 socket returned where notification mesages will appear + * @retval 0 OK + * @retval -1 Error and logged to syslog + * @note When using netconf create-subsrciption,status and format is not supported */ int @@ -709,11 +765,14 @@ clicon_rpc_create_subscription(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("" + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("" "%s" "%s" "", + username?username:"", stream?stream:"", filter?filter:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, s0) < 0) @@ -732,8 +791,10 @@ clicon_rpc_create_subscription(clicon_handle h, } /*! Send a debug request to backend server - * @param[in] h CLICON handle - * @param[in] level Debug level + * @param[in] h CLICON handle + * @param[in] level Debug level + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_debug(clicon_handle h, @@ -743,8 +804,10 @@ clicon_rpc_debug(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("%d", level)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index d02bf5ff..d9892e90 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -140,7 +140,7 @@ clicon_strjoin(int argc, } static int -unreserved(unsigned char in) +uri_unreserved(unsigned char in) { switch(in) { case '0': case '1': case '2': case '3': case '4': @@ -163,12 +163,18 @@ unreserved(unsigned char in) return 0; } -/*! Percent encoding according to RFC 3896 - * @param[out] esc Deallocate with free() +/*! Percent encoding according to RFC 3986 URI Syntax + * @param[in] str Not-encoded input string + * @param[out] escp Encoded/escaped malloced output string + * @retval 0 OK + * @retval -1 Error + * @see RFC 3986 Uniform Resource Identifier (URI): Generic Syntax + * @see uri_percent_decode + * @see xml_chardata_encode */ int -percent_encode(char *str, - char **escp) +uri_percent_encode(char *str, + char **escp) { int retval = -1; char *esc = NULL; @@ -184,7 +190,7 @@ percent_encode(char *str, memset(esc, 0, len); j = 0; for (i=0; i "& " must + * < -> "< " must + * > -> "> " must for backward compatibility + * ' -> "' " may + * ' -> "" " may + * Optionally > + */ +int +xml_chardata_encode(char *str, + char **escp) +{ + int retval = -1; + char *esc = NULL; + int l; + int len; + int i, j; + + len = 0; + for (i=0; i': + len += strlen("> "); + break; + default: + len++; + } + } + len++; /* trailing \0 */ + if ((esc = malloc(len)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(esc, 0, len); + j = 0; + for (i=0; i': + if ((l=snprintf(&esc[j], 6, "> ")) < 0){ + clicon_err(OE_UNIX, errno, "snprintf"); + goto done; + } + j += l; + break; + default: + esc[j++] = str[i]; + } + } + *escp = esc; + retval = 0; + done: + if (retval < 0 && esc) + free(esc); + return retval; +} + /*! Split a string into a cligen variable vector using 1st and 2nd delimiter * Split a string first into elements delimited by delim1, then into * pairs delimited by delim2. @@ -295,7 +398,7 @@ str2cvec(char *string, *(snext++) = '\0'; if ((val = index(s, delim2)) != NULL){ *(val++) = '\0'; - if (percent_decode(val, &valu) < 0) + if (uri_percent_decode(val, &valu) < 0) goto err; if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_add"); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 75286ed2..15e25d1a 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -57,7 +57,6 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_string.h" - #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" @@ -165,7 +164,7 @@ xml_name_set(cxobj *xn, } if (name){ if ((xn->x_name = strdup(name)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -198,7 +197,7 @@ xml_namespace_set(cxobj *xn, } if (namespace){ if ((xn->x_namespace = strdup(namespace)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -289,7 +288,7 @@ xml_value_set(cxobj *xn, } if (val){ if ((xn->x_value = strdup(val)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -434,7 +433,7 @@ xml_child_i_set(cxobj *xt, /*! Iterator over xml children objects * - * NOTE: Never manipulate the child-list during operation or using the + * @note Never manipulate the child-list during operation or using the * same object recursively, the function uses an internal field to remember the * index used. It works as long as the same object is not iterated concurrently. * @@ -481,7 +480,7 @@ xml_child_append(cxobj *x, x->x_childvec_len++; x->x_childvec = realloc(x->x_childvec, x->x_childvec_len*sizeof(cxobj*)); if (x->x_childvec == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); return -1; } x->x_childvec[x->x_childvec_len-1] = xc; @@ -539,7 +538,7 @@ xml_new(char *name, cxobj *x; if ((x = malloc(sizeof(cxobj))) == NULL){ - clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_XML, errno, "malloc"); return NULL; } memset(x, 0, sizeof(cxobj)); @@ -951,7 +950,7 @@ xml_free(cxobj *x) * XML printing functions. Output a parse tree to file, string cligen buf *------------------------------------------------------------------------*/ -/*! Print an XML tree structure to an output stream +/*! Print an XML tree structure to an output stream and encode chars "<>&" * * Uses clicon_xml2cbuf internally * @@ -976,13 +975,17 @@ clicon_xml2file(FILE *f, int hasbody; int haselement; char *val; - + char *encstr = NULL; /* xml encoded string */ + name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ case CX_BODY: - if ((val = xml_value(x)) != NULL) /* incomplete tree */ - fprintf(f, "%s", xml_value(x)); + if ((val = xml_value(x)) == NULL) /* incomplete tree */ + break; + if (xml_chardata_encode(val, &encstr) < 0) + goto done; + fprintf(f, "%s", encstr); break; case CX_ATTR: fprintf(f, " "); @@ -1045,6 +1048,8 @@ clicon_xml2file(FILE *f, }/* switch */ retval = 0; done: + if (encstr) + free(encstr); return retval; } @@ -1064,8 +1069,7 @@ xml_print(FILE *f, return clicon_xml2file(f, xn, 0, 1); } - -/*! Print an XML tree structure to a cligen buffer +/*! Print an XML tree structure to a cligen buffer and encode chars "<>&" * * @param[in,out] cb Cligen buffer to write to * @param[in] xn Clicon xml tree @@ -1094,12 +1098,18 @@ clicon_xml2cbuf(cbuf *cb, int hasbody; int haselement; char *namespace; - + char *encstr = NULL; /* xml encoded string */ + char *val; + name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ case CX_BODY: - cprintf(cb, "%s", xml_value(x)); + if ((val = xml_value(x)) == NULL) /* incomplete tree */ + break; + if (xml_chardata_encode(val, &encstr) < 0) + goto done; + cprintf(cb, "%s", encstr); break; case CX_ATTR: cprintf(cb, " "); @@ -1131,7 +1141,6 @@ clicon_xml2cbuf(cbuf *cb, default: break; } - /* Check for special case instead of */ if (hasbody==0 && haselement==0) cprintf(cb, "/>"); @@ -1159,6 +1168,8 @@ clicon_xml2cbuf(cbuf *cb, }/* switch */ retval = 0; done: + if (encstr) + free(encstr); return retval; } /*! Print actual xml tree datastructures (not xml), mainly for debugging @@ -1215,8 +1226,8 @@ xmltree2cbuf(cbuf *cb, */ static int _xml_parse(const char *str, - yang_spec *yspec, - cxobj *xt) + yang_spec *yspec, + cxobj *xt) { int retval = -1; struct xml_parse_yacc_arg ya = {0,}; @@ -1303,15 +1314,14 @@ xml_parse_file(int fd, if (endtag != NULL) endtaglen = strlen(endtag); if ((xmlbuf = malloc(xmlbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_XML, errno, "malloc"); goto done; } memset(xmlbuf, 0, xmlbuflen); ptr = xmlbuf; while (1){ if ((ret = read(fd, &ch, 1)) < 0){ - clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n", - __FUNCTION__, + clicon_err(OE_XML, errno, "read: [pid:%d]\n", (int)getpid()); break; } @@ -1334,7 +1344,7 @@ xml_parse_file(int fd, oldxmlbuflen = xmlbuflen; xmlbuflen *= 2; if ((xmlbuf = realloc(xmlbuf, xmlbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); goto done; } memset(xmlbuf+oldxmlbuflen, 0, xmlbuflen-oldxmlbuflen); @@ -1424,10 +1434,7 @@ xml_parse_va(cxobj **xtop, va_start(args, format); len = vsnprintf(str, len, format, args) + 1; va_end(args); - if (*xtop == NULL) - if ((*xtop = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) - goto done; - if (_xml_parse(str, yspec, *xtop) < 0) + if (xml_parse_string(str, yspec, xtop) < 0) goto done; retval = 0; done: @@ -1447,7 +1454,7 @@ xml_copy_one(cxobj *x0, xml_type_set(x1, xml_type(x0)); if (xml_value(x0)){ /* malloced string */ if ((x1->x_value = strdup(x0->x_value)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -1456,7 +1463,7 @@ xml_copy_one(cxobj *x0, return -1; if (xml_cv_get(x0)){ if ((cv1 = cv_dup(xml_cv_get(x0))) == NULL){ - clicon_err(OE_XML, errno, "%s: cv_dup", __FUNCTION__); + clicon_err(OE_XML, errno, "cv_dup"); return -1; } if ((xml_cv_set(x1, cv1)) < 0) @@ -1553,7 +1560,7 @@ cxvec_append(cxobj *x, int retval = -1; if ((*vec = realloc(*vec, sizeof(cxobj *) * (*len+1))) == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); goto done; } (*vec)[(*len)++] = x; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index d3738f37..8702910d 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -43,9 +43,11 @@ #include #include #include +#include #include #include -#include +#include + /* cligen */ #include @@ -322,7 +324,7 @@ xmldb_setopt(clicon_handle h, * @param[in] dbname Name of database to search in (filename including dir path * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] config If set only configuration data, else also state - * @param[out] xtop Single XML tree. Free with xml_free() + * @param[out] xret Single return XML tree. Free with xml_free() * @retval 0 OK * @retval -1 Error * @code @@ -339,7 +341,7 @@ xmldb_get(clicon_handle h, const char *db, char *xpath, int config, - cxobj **xtop) + cxobj **xret) { int retval = -1; xmldb_handle xh; @@ -357,11 +359,11 @@ xmldb_get(clicon_handle h, clicon_err(OE_DB, 0, "Not connected to datastore plugin"); goto done; } - retval = xa->xa_get_fn(xh, db, xpath, config, xtop); + retval = xa->xa_get_fn(xh, db, xpath, config, xret); #if DEBUG if (retval == 0) { cbuf *cb = cbuf_new(); - clicon_xml2cbuf(cb, *xtop, 0, 0); + clicon_xml2cbuf(cb, *xret, 0, 0); clicon_log(LOG_WARNING, "%s: db:%s xpath:%s xml:%s", __FUNCTION__, db, xpath, cbuf_get(cb)); cbuf_free(cb); @@ -377,25 +379,29 @@ xmldb_get(clicon_handle h, * @param[in] db running or candidate * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy + * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". * @retval 0 OK * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code * cxobj *xt; + * cxobj *xret = NULL; * if (xml_parse_string("17", yspec, &xt) < 0) * err; - * if (xmldb_put(xh, "running", OP_MERGE, xt) < 0) + * if (xmldb_put(xh, "running", OP_MERGE, xt, cbret) < 0) * err; * @endcode * @note that you can add both config data and state data. In comparison, * xmldb_get has a parameter to get config data only. + * @note if xret is non-null, it may contain error message * */ int xmldb_put(clicon_handle h, const char *db, enum operation_type op, - cxobj *xt) + cxobj *xt, + cbuf *cbret) { int retval = -1; xmldb_handle xh; @@ -425,7 +431,7 @@ xmldb_put(clicon_handle h, cbuf_free(cb); } #endif - retval = xa->xa_put_fn(xh, db, op, xt); + retval = xa->xa_put_fn(xh, db, op, xt, cbret); done: return retval; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 105b8661..1be9d750 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -61,10 +61,11 @@ #include #include #include -#include -#include #include #include +#include +#include +#include #include /* cligen */ @@ -79,28 +80,15 @@ #include "clixon_string.h" #include "clixon_yang.h" #include "clixon_yang_type.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_log.h" #include "clixon_err.h" #include "clixon_xml_sort.h" #include "clixon_xml_map.h" -/* - * A node is a leaf if it contains a body. - */ -static cxobj * -leaf(cxobj *xn) -{ - cxobj *xc = NULL; - - while ((xc = xml_child_each(xn, xc, CX_BODY)) != NULL) - break; - return xc; -} - /*! x is element and has eactly one child which in turn has none */ static int tleaf(cxobj *x) @@ -117,6 +105,7 @@ tleaf(cxobj *x) /*! Translate XML -> TEXT * @param[in] level print 4 spaces per level in front of each line + * XXX rewrite using YANG and remove encrypted password KLUDGE */ int xml2txt(FILE *f, @@ -127,26 +116,19 @@ xml2txt(FILE *f, int children=0; char *term = NULL; int retval = -1; - int encr=0; xe = NULL; /* count children */ while ((xe = xml_child_each(x, xe, -1)) != NULL) children++; if (!children){ if (xml_type(x) == CX_BODY){ - /* Kludge for escaping encrypted passwords */ - if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0) - encr++; term = xml_value(x); } else{ fprintf(f, "%*s", 4*level, ""); term = xml_name(x); } - if (encr) - fprintf(f, "\"%s\";\n", term); - else - fprintf(f, "%s;\n", term); + fprintf(f, "%s;\n", term); retval = 0; goto done; } @@ -183,67 +165,60 @@ xml2cli(FILE *f, { int retval = -1; cxobj *xe = NULL; - char *term; - int bool; - int nr; - int i; - cbuf *cbpre; + cbuf *cbpre = NULL; + yang_stmt *ys; + int match; + char *body; + ys = xml_spec(x); + if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ + if (prepend0) + fprintf(f, "%s", prepend0); + body = xml_body(x); + if (gt == GT_ALL || gt == GT_VARS) + fprintf(f, "%s ", xml_name(x)); + if (index(body, ' ')) + fprintf(f, "\"%s\"", body); + else + fprintf(f, "%s", body); + fprintf(f, "\n"); + goto ok; + } /* Create prepend variable string */ if ((cbpre = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - nr = xml_child_nr(x); - if (!nr){ - if (xml_type(x) == CX_BODY) - term = xml_value(x); - else - term = xml_name(x); - if (prepend0) - fprintf(f, "%s ", prepend0); - if (index(term, ' ')) - fprintf(f, "\"%s\"\n", term); - else - fprintf(f, "%s\n", term); - retval = 0; - goto done; - } if (prepend0) cprintf(cbpre, "%s", prepend0); -/* bool determines when to print a variable keyword: - !leaf T for all (ie parameter) - index GT_NONE F - index GT_VARS F - index GT_ALL T - !index GT_NONE F - !index GT_VARS T - !index GT_ALL T - */ - bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS); -// bool = (!x->xn_index || gt == GT_ALL); - if (bool){ - if (cbuf_len(cbpre)) - cprintf(cbpre, " "); - cprintf(cbpre, "%s", xml_name(x)); - } - xe = NULL; - /* First child is unique, then add that, before looping. */ - i = 0; - while ((xe = xml_child_each(x, xe, -1)) != NULL){ - /* Dont call this if it is index and there are other following */ - if (0 && i < nr-1) - ; - else - if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) + cprintf(cbpre, "%s ", xml_name(x)); + + if (ys->ys_keyword == Y_LIST){ + /* If list then first loop through keys */ + xe = NULL; + while ((xe = xml_child_each(x, xe, -1)) != NULL){ + if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0) goto done; - if (0){ /* assume index is first, otherwise need one more while */ + if (!match) + continue; if (gt == GT_ALL) - cprintf(cbpre, " %s", xml_name(xe)); - cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0))); + cprintf(cbpre, "%s ", xml_name(xe)); + cprintf(cbpre, "%s ", xml_body(xe)); } - i++; } + /* Then loop through all other (non-keys) */ + xe = NULL; + while ((xe = xml_child_each(x, xe, -1)) != NULL){ + if (ys->ys_keyword == Y_LIST){ + if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0) + goto done; + if (match) + continue; + } + if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) + goto done; + } + ok: retval = 0; done: if (cbpre) @@ -268,7 +243,6 @@ validate_leafref(cxobj *xt, char *leafrefbody; char *leafbody; - if ((leafrefbody = xml_body(xt)) == NULL) return 0; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ @@ -316,9 +290,11 @@ xml_yang_validate_add(cxobj *xt, yang_stmt *ys; char *body; - /* if not given by argument (overide) use default link */ - if ((ys = xml_spec(xt)) != NULL){ + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ switch (ys->ys_keyword){ + case Y_INPUT: case Y_LIST: /* fall thru */ case Y_CONTAINER: @@ -326,6 +302,8 @@ xml_yang_validate_add(cxobj *xt, yc = ys->ys_stmt[i]; if (yc->ys_keyword != Y_LEAF) continue; + if (yang_config(yc)==0) + continue; if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", yc->ys_argument); @@ -385,8 +363,10 @@ xml_yang_validate_all(cxobj *xt, yang_stmt *ys; yang_stmt *ytype; - /* if not given by argument (overide) use default link */ - if ((ys = xml_spec(xt)) != NULL){ + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((ys = xml_spec(xt)) != NULL && + yang_config(ys) != 0){ switch (ys->ys_keyword){ case Y_LEAF: /* fall thru */ @@ -587,7 +567,7 @@ yang_next(yang_node *y, yang_stmt *ys; if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, 0); + ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); else ys = yang_find_datanode(y, name); if (ys == NULL) @@ -833,7 +813,6 @@ yang2api_path_fmt(yang_stmt *ys, return retval; } - /*! Transform an xml key format and a vector of values to an XML key * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() * Example: @@ -896,7 +875,7 @@ api_path_fmt2api_path(char *api_path_fmt, clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } - if (percent_encode(str, &strenc) < 0) + if (uri_percent_encode(str, &strenc) < 0) goto done; cprintf(cb, "%s", strenc); free(strenc); strenc = NULL; @@ -1313,7 +1292,7 @@ xml_spec_populate(cxobj *x, (yp = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yp, xml_name(x)); else - y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ #endif if (y) xml_spec_set(x, y); @@ -1367,7 +1346,7 @@ api_path2xpath_cvv(yang_spec *yspec, clicon_debug(1, "[%d] cvname:%s", i, name); clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0)); if (i == offset){ - if ((y = yang_find_topnode(yspec, name, 0)) == NULL){ + if ((y = yang_find_topnode(yspec, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1448,7 +1427,7 @@ api_path2xpath(yang_spec *yspec, * @param[in] nvec Length of vec * @param[in] x0 Xpath tree so far * @param[in] y0 Yang spec for x0 - * @param[in] schemanode If set use schema nodes otherwise data nodes. + * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml @@ -1458,7 +1437,7 @@ api_path2xml_vec(char **vec, int nvec, cxobj *x0, yang_node *y0, - int schemanode, + yang_class nodeclass, cxobj **xpathp, yang_node **ypathp) { @@ -1490,7 +1469,7 @@ api_path2xml_vec(char **vec, if ((restval_enc = index(name, '=')) != NULL){ *restval_enc = '\0'; restval_enc++; - if (percent_decode(restval_enc, &restval) < 0) + if (uri_percent_decode(restval_enc, &restval) < 0) goto done; } /* Split into prefix and localname, ignore prefix for now */ @@ -1500,10 +1479,10 @@ api_path2xml_vec(char **vec, name = local; } if (y0->yn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, schemanode); + y = yang_find_topnode((yang_spec*)y0, name, nodeclass); } else { - y = schemanode?yang_find_schemanode((yang_node*)y0, name): + y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): yang_find_datanode((yang_node*)y0, name); } if (y == NULL){ @@ -1572,7 +1551,7 @@ api_path2xml_vec(char **vec, } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, - schemanode, + nodeclass, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1588,7 +1567,7 @@ api_path2xml_vec(char **vec, * @param[in] api_path API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree - * @param[in] schemanode If set use schema nodes otherwise data nodes. + * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp * @example @@ -1605,7 +1584,7 @@ int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, - int schemanode, + yang_class nodeclass, cxobj **xbotp, yang_node **ybotp) { @@ -1614,8 +1593,8 @@ api_path2xml(char *api_path, int nvec; if (*api_path!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", api_path); - goto done; + clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path); + goto ok; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; @@ -1628,9 +1607,10 @@ api_path2xml(char *api_path, } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, schemanode, + xtop, (yang_node*)yspec, nodeclass, xbotp, ybotp) < 0) goto done; + ok: retval = 0; done: if (vec) @@ -1643,6 +1623,9 @@ api_path2xml(char *api_path, * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base + * @param[out] reason If retval=0 a malloced string + * @retval 0 OK. If reason is set, Yang error + * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ @@ -1650,7 +1633,8 @@ static int xml_merge1(cxobj *x0, yang_node *y0, cxobj *x0p, - cxobj *x1) + cxobj *x1, + char **reason) { int retval = -1; char *x1name; @@ -1698,24 +1682,35 @@ xml_merge1(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ - clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); - goto done; + if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; } /* See if there is a corresponding node in the base tree */ x0c = NULL; if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; + if (*reason != NULL) + goto ok; } } /* else Y_CONTAINER */ - // ok: + ok: retval = 0; done: return retval; } /*! Merge XML trees x1 into x0 according to yang spec yspec + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] x1 xml tree which modifies base + * @param[in] yspec Yang spec + * @param[out] reason If retval=0 a malloced string. Needs to be freed by caller + * @retval 0 OK. If reason is set, Yang error + * @retval -1 Error * @note both x0 and x1 need to be top-level trees * @see text_modify_top as more generic variant (in datastore text) * @note returns -1 if YANG do not match, you may want to have a softer error @@ -1723,7 +1718,8 @@ xml_merge1(cxobj *x0, int xml_merge(cxobj *x0, cxobj *x1, - yang_spec *yspec) + yang_spec *yspec, + char **reason) { int retval = -1; char *x1cname; /* child name */ @@ -1736,17 +1732,22 @@ xml_merge(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ + if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; + if (*reason != NULL) + break; } - retval = 0; + retval = 0; /* OK */ done: return retval; } diff --git a/lib/src/clixon_xml_parse.h b/lib/src/clixon_xml_parse.h index 83950abb..3122e8f0 100644 --- a/lib/src/clixon_xml_parse.h +++ b/lib/src/clixon_xml_parse.h @@ -51,6 +51,7 @@ struct xml_parse_yacc_arg{ cxobj *ya_xparent; /* xml parent element*/ int ya_skipspace; /* If set, remove all non-terminal bodies (strip pretty-print) */ yang_spec *ya_yspec; /* If set, top-level yang-spec */ + int ya_lex_state; /* lex start condition (AMPERSAND) */ }; extern char *clixon_xml_parsetext; diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index e16ffaae..e0c39884 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -76,6 +76,7 @@ int clixon_xml_parsewrap(void) %x START %s STATEA +%s AMPERSAND %s CMNT %s STR %s TEXTDECL @@ -88,24 +89,31 @@ int clixon_xml_parsewrap(void) } [ \t]+ ; \: return *clixon_xml_parsetext; -\n { _YA->ya_linenum++;} -""/>" { BEGIN(STATEA); return ESLASH; } -"" { BEGIN(START); return ECOMMENT; } \n _YA->ya_linenum++; @@ -117,15 +125,15 @@ int clixon_xml_parsewrap(void) \" { BEGIN(STRDQ); return *clixon_xml_parsetext; } \' { BEGIN(STRSQ); return *clixon_xml_parsetext; } -[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \" { BEGIN(START); return *clixon_xml_parsetext; } -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } -[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \" { BEGIN(TEXTDECL); return *clixon_xml_parsetext; } -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } -[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \' { BEGIN(TEXTDECL); return *clixon_xml_parsetext; } %% diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 1e774e29..ab9d5193 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -41,7 +41,7 @@ %start topxml -%token NAME CHAR +%token NAME CHARDATA %token VER ENC %token BSLASH ESLASH %token BTEXT ETEXT @@ -329,15 +329,15 @@ topxml : list dcl : BTEXT info encode ETEXT { clicon_debug(3, "dcl->info encode"); } ; -info : VER '=' '\"' CHAR '\"' +info : VER '=' '\"' CHARDATA '\"' { if (xml_parse_version(_YA, $4) <0) YYABORT; } - | VER '=' '\'' CHAR '\'' + | VER '=' '\'' CHARDATA '\'' { if (xml_parse_version(_YA, $4) <0) YYABORT; } | ; -encode : ENC '=' '\"' CHAR '\"' {free($4);} - | ENC '=' '\'' CHAR '\'' {free($4);} +encode : ENC '=' '\"' CHARDATA '\"' {free($4);} + | ENC '=' '\'' CHARDATA '\'' {free($4);} ; element : '<' qname attrs element1 @@ -372,8 +372,8 @@ list : list content { clicon_debug(3, "list -> list content"); } content : element { clicon_debug(3, "content -> element"); } | comment { clicon_debug(3, "content -> comment"); } - | CHAR { if (xml_parse_content(_YA, $1) < 0) YYABORT; - clicon_debug(3, "content -> CHAR %s", $1); } + | CHARDATA { if (xml_parse_content(_YA, $1) < 0) YYABORT; + clicon_debug(3, "content -> CHARDATA %s", $1); } | { clicon_debug(3, "content -> "); } ; @@ -394,7 +394,7 @@ attqname : NAME {$$ = $1;} ; -attvalue : '\"' CHAR '\"' { $$=$2; /* $2 must be consumed */} +attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */} | '\"' '\"' { $$=strdup(""); /* $2 must be consumed */} ; diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index ed2e987e..e3aa4b23 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -93,7 +93,7 @@ xml_child_spec(char *name, if (xp && (yparent = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yparent, name); else if (yspec) - y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ else y = NULL; *yresult = y; @@ -167,7 +167,7 @@ xml_cmp(const void* arg1, return equal; } -/*! +/*! Compare xml object * @param[in] yangi Yang order * @param[in] keynr Length of keyvec/keyval vector when applicable * @param[in] keyvec Array of of yang key identifiers @@ -192,36 +192,38 @@ xml_cmp1(cxobj *x, int i; char *keyname; char *key; + int match = 0; /* Check if same yang spec (order in yang stmt list) */ switch (keyword){ case Y_CONTAINER: /* Match with name */ case Y_LEAF: /* Match with name */ - return strcmp(name, xml_name(x)); + match = strcmp(name, xml_name(x)); break; case Y_LEAF_LIST: /* Match with name and value */ if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL) *userorder=1; b=xml_body(x); - return strcmp(keyval[0], b); + match = strcmp(keyval[0], b); break; case Y_LIST: /* Match with array of key values */ if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL) *userorder=1; + /* All must match */ for (i=0; ie0 given "name" */ if ((b = xml_find_body(x, keyname)) == NULL) break; /* error case */ - return strcmp(key, b); + if ((match = strcmp(key, b)) != 0) + break; } - return 0; break; default: break; } - return 0; /* should not reach here */ + return match; /* should not reach here */ } /*! Sort children of an XML node @@ -273,12 +275,12 @@ xml_search_userorder(cxobj *x0, } /*! - * @param[in] yangi Yang order - * @param[in] keynr Length of keyvec/keyval vector when applicable - * @param[in] keyvec Array of of yang key identifiers - * @param[in] keyval Array of of yang key values - * @param[in] low Lower bound of childvec search interval - * @param[in] upper Lower bound of childvec search interval + * @param[in] yangi Yang order + * @param[in] keynr Length of keyvec/keyval vector when applicable + * @param[in] keyvec Array of of yang key identifiers + * @param[in] keyval Array of of yang key values + * @param[in] low Lower bound of childvec search interval + * @param[in] upper Lower bound of childvec search interval */ static cxobj * xml_search1(cxobj *x0, @@ -522,7 +524,7 @@ xml_sort_verify(cxobj *x0, return retval; } -/*! Given child tree x1c, find matching child in base tree x0 +/*! Given child tree x1c, find matching child in base tree x0 and return as x0cp * param[in] x0 Base tree node * param[in] x1c Modification tree child * param[in] yc Yang spec of tree child @@ -567,7 +569,10 @@ match_base_child(cxobj *x0, break; case Y_LIST: /* Match with key values */ cvk = yc->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ - /* Count number of key indexes */ + /* Count number of key indexes + * Then create two vectors one with names and one with values of x1c, + * ec: keyvec: [a,b,c] keyval: [1,2,3] + */ cvi = NULL; keynr = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) keynr++; @@ -591,7 +596,7 @@ match_base_child(cxobj *x0, default: break; } - /* Get match */ + /* Get match. Sorting mode(optimized) or not?*/ if (xml_child_sort==0) *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); else{ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 1014b0f0..f15057a1 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -32,7 +32,9 @@ ***** END LICENSE BLOCK ***** * Yang functions - */ + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 + */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -66,6 +68,7 @@ #include "clixon_file.h" #include "clixon_yang.h" #include "clixon_hash.h" +#include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_yang_type.h" @@ -133,7 +136,8 @@ static const map_str2int ykmap[] = { {"type", Y_TYPE}, {"typedef", Y_TYPEDEF}, {"unique", Y_UNIQUE}, - {"units", Y_UNITS}, + {"units", Y_UNITS}, + {"unknown", Y_UNKNOWN}, {"uses", Y_USES}, {"value", Y_VALUE}, {"when", Y_WHEN}, @@ -401,6 +405,94 @@ yang_find(yang_node *yn, } return match ? ys : NULL; } +#ifdef NOTYET +/*! Prototype more generic than yang_find_datanode and yang_find_schemanode + */ +yang_stmt * +yang_find_class(yang_node *yn, + char *argument, + yang_class class) +{ + yang_stmt *ys = NULL; + yang_stmt *yc = NULL; + yang_stmt *ysmatch = NULL; + int i, j; + int ok; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + switch(class){ + case YC_NONE: + ok = 1; + break; + case YC_DATANODE: + ok = yang_datanode(ys); + break; + case YC_DATADEFINITION: + ok = yang_datadefinition(ys); + break; + case YC_SCHEMANODE: + ok = yang_schemanode(ys); + break; + } + if (!ok) + continue; + switch(class){ + case YC_NONE: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_DATANODE: + case YC_DATADEFINITION: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_SCHEMANODE: + if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ + for (j=0; jys_len; j++){ + yc = ys->ys_stmt[j]; + if (yc->ys_keyword == Y_CASE) /* Look for its children */ + ysmatch = yang_find_class((yang_node*)yc, argument, class); + else{ + if (yang_schemanode(yc)){ + if (argument == NULL) + ysmatch = yc; + else + if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) + ysmatch = yc; + } + } + if (ysmatch) + goto match; + } + } /* Y_CHOICE */ + else{ + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + + } + break; + } /* switch */ + } /* for */ + match: + return ysmatch; +} +#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -454,6 +546,8 @@ yang_find_datanode(yang_node *yn, } /*! Find child schema node with matching argument (container, leaf, etc) + * @param[in] yn Yang node, current context node. + * @param[in] argument if NULL, match any(first) argument. * @note XXX unify code with yang_find_datanode? * @see yang_find_datanode */ @@ -504,7 +598,7 @@ yang_find_schemanode(yang_node *yn, /*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? * @param[in] schemanode If set look for schema nodes, otherwise only data nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to @@ -513,8 +607,8 @@ yang_find_schemanode(yang_node *yn, */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *name, - int schemanode) + char *argument, + yang_class class) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -522,13 +616,22 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if (schemanode){ - if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) + switch (class){ + case YC_NONE: + if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL) return yc; + break; + case YC_DATANODE: + if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_SCHEMANODE: + if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_DATADEFINITION: + break; /* nyi */ } - else - if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) - return yc; } return NULL; } @@ -668,13 +771,15 @@ ys_spec(yang_stmt *ys) return (yang_spec*)ys; } -/* Extract id from type argument. two cases: - * argument is prefix:id, - * argument is id, +/* Assume argument is id on the type: <[prefix:]id>, return 'id' * Just return string from id + * @param[in] ys A yang statement + * @retval NULL No id (argument is NULL) + * @retval id Pointer to identifier + * @see yarg_prefix */ char* -ytype_id(yang_stmt *ys) +yarg_id(yang_stmt *ys) { char *id; @@ -685,13 +790,14 @@ ytype_id(yang_stmt *ys) return id; } -/* Extract prefix from type argument. two cases: - * argument is prefix:id, - * argument is id, - * return either NULL or a new prefix string that needs to be freed by caller. +/* Assume argument is id on the type: <[prefix:]id>, return 'prefix' + * @param[in] ys A yang statement + * @retval NULL No prefix + * @retval prefix Malloced string that needs to be freed by caller. + * @see yarg_id */ char* -ytype_prefix(yang_stmt *ys) +yarg_prefix(yang_stmt *ys) { char *id; char *prefix = NULL; @@ -822,7 +928,11 @@ yang_print_cbuf(cbuf *cb, yang_stmt *ys = NULL; while ((ys = yn_each(yn, ys)) != NULL) { - cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword)); + if (ys->ys_keyword == Y_UNKNOWN){ /* dont print unknown - proxy for extension*/ + cprintf(cb, "%*s", marginal-1, ""); + } + else + cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword)); if (ys->ys_argument){ if (quotedstring(ys->ys_argument)) cprintf(cb, " \"%s\"", ys->ys_argument); @@ -974,7 +1084,7 @@ ys_populate_range(yang_stmt *ys, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; - origtype = ytype_id((yang_stmt*)yparent); + origtype = yarg_id((yang_stmt*)yparent); /* This handles non-resolved also */ if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; @@ -1209,7 +1319,7 @@ yang_augment_node(yang_stmt *ys, clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); /* Find the target */ - if (yang_abs_schema_nodeid(ysp, schema_nodeid, &yss) < 0) + if (yang_abs_schema_nodeid(ysp, schema_nodeid, -1, &yss) < 0) goto done; if (yss == NULL) goto ok; @@ -1291,8 +1401,8 @@ yang_expand(yang_node *yn) switch(ys->ys_keyword){ case Y_USES: /* Split argument into prefix and name */ - name = ytype_id(ys); /* This is uses/grouping name to resolve */ - prefix = ytype_prefix(ys); /* And this its prefix */ + name = yarg_id(ys); /* This is uses/grouping name to resolve */ + prefix = yarg_prefix(ys); /* And this its prefix */ if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0) goto done; if (prefix) @@ -1638,7 +1748,7 @@ ys_schemanode_check(yang_stmt *ys, /* fallthru */ case Y_REFINE: case Y_UNIQUE: - if (yang_desc_schema_nodeid(yp, ys->ys_argument, &yres) < 0) + if (yang_desc_schema_nodeid(yp, ys->ys_argument, -1, &yres) < 0) goto done; if (yres == NULL){ clicon_err(OE_YANG, 0, "schemanode sanity check of %d %s", @@ -1649,7 +1759,7 @@ ys_schemanode_check(yang_stmt *ys, break; case Y_DEVIATION: yspec = ys_spec(ys); - if (yang_abs_schema_nodeid(yspec, ys->ys_argument, &yres) < 0) + if (yang_abs_schema_nodeid(yspec, ys->ys_argument, -1, &yres) < 0) goto done; if (yres == NULL){ clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument); @@ -1792,6 +1902,7 @@ yang_apply(yang_node *yn, * @param[in] yn Yang node. Find next yang stmt and return that if match. * @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b * @param[in] nvec Length of vec + * @param[in] keyword A schemode of this type, or -1 if any * @param[out] yres Result yang statement node, or NULL if not found * @retval -1 Error, with clicon_err called * @retval 0 OK @@ -1800,6 +1911,7 @@ static int schema_nodeid_vec(yang_node *yn, char **vec, int nvec, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -1830,6 +1942,8 @@ schema_nodeid_vec(yang_node *yn, ys = yn->yn_stmt[i]; if (!yang_schemanode(ys)) continue; + if (keyword != -1 && keyword != ys->ys_keyword) + continue; /* some keys dont have arguments, match on key */ if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){ @@ -1856,7 +1970,7 @@ schema_nodeid_vec(yang_node *yn, goto ok; } /* recursive call using ynext */ - if (schema_nodeid_vec(ynext, vec+1, nvec-1, yres) < 0) + if (schema_nodeid_vec(ynext, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: retval = 0; @@ -1867,15 +1981,22 @@ schema_nodeid_vec(yang_node *yn, /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec * @param[in] yspec Yang specification. * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid + * @param[in] keyword A schemode of this type, or -1 if any + * @param[out] yres Result yang statement node, or NULL if not found + * @retval -1 Error, with clicon_err called + * @retval 0 OK (if yres set then found, if yres=0 then not found) * Assume schema nodeid:s have prefixes, (actually the first). * @see yang_desc_schema_nodeid + * @see RFC7950 6.5 + o schema node: A node in the schema tree. One of action, container, + leaf, leaf-list, list, choice, case, rpc, input, output, + notification, anydata, and anyxml. * Used in yang: deviation, top-level augment */ int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -1885,6 +2006,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, char *id; char *prefix = NULL; yang_stmt *yprefix; + yang_stmt *ys; /* check absolute schema_nodeid */ if (schema_nodeid[0] != '/'){ @@ -1903,8 +2025,8 @@ yang_abs_schema_nodeid(yang_spec *yspec, } /* split : */ if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */ - clicon_err(OE_YANG, 0, "Absolute schema nodeid must have prefix"); - goto done; + clicon_log(LOG_WARNING, "%s: Absolute schema nodeid must have prefix", __FUNCTION__); + goto ok; } if ((prefix = strdup(vec[1])) == NULL){ clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); @@ -1920,8 +2042,8 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } if (ymod == NULL){ /* Try with topnode */ - yang_stmt *ys; - if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ + + if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); goto done; } @@ -1930,8 +2052,9 @@ yang_abs_schema_nodeid(yang_spec *yspec, goto done; } } - if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) + if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; + ok: /* yres may not be set */ retval = 0; done: if (vec) @@ -1944,14 +2067,17 @@ yang_abs_schema_nodeid(yang_spec *yspec, /*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec * @param[in] yn Yang node * @param[in] schema_nodeid Descendant schema-node-id, ie a/b - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid + * @param[in] keyword A schemode of this type, or -1 if any + * @param[out] yres First yang node matching schema nodeid + * @retval 0 OK + * @retval -1 Error, with clicon_err called * @see yang_schema_nodeid * Used in yang: unique, refine, uses augment */ int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -1967,7 +2093,7 @@ yang_desc_schema_nodeid(yang_node *yn, } if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL) goto done; - if (schema_nodeid_vec(yn, vec, nvec, yres) < 0) + if (schema_nodeid_vec(yn, vec, nvec, keyword, yres) < 0) goto done; retval = 0; done: @@ -2016,6 +2142,8 @@ ys_parse(yang_stmt *ys, * Specific syntax checks and variable creation for stand-alone yang statements. * That is, siblings, etc, may not be there. Complete checks are made in * ys_populate instead. + * @param[in] ys yang statement + * @param[in] extra Yang extra for cornercases (unknown/extension) * * The cv:s created in parse-tree as follows: * fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass) @@ -2023,13 +2151,19 @@ ys_parse(yang_stmt *ys, * @see ys_populate */ int -ys_parse_sub(yang_stmt *ys) +ys_parse_sub(yang_stmt *ys, + char *extra) { int retval = -1; - + int cvret; + char *reason = NULL; + yang_stmt *ymod; + uint8_t fd; + char *prefix = NULL; + char *name; + switch (ys->ys_keyword){ - case Y_FRACTION_DIGITS:{ - uint8_t fd; + case Y_FRACTION_DIGITS: if (ys_parse(ys, CGV_UINT8) == NULL) goto done; fd = cv_uint8_get(ys->ys_cv); @@ -2038,12 +2172,41 @@ ys_parse_sub(yang_stmt *ys) goto done; } break; - } + case Y_UNKNOWN: + if (extra == NULL) + break; + /* Find extension, if found, store it as unknown, if not, + break for error */ + prefix = yarg_prefix(ys); /* And this its prefix */ + name = yarg_id(ys); /* This is the type to resolve */ + if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) + goto ok; /* shouldnt happen */ + if (yang_find((yang_node*)ymod, Y_EXTENSION, name) == NULL){ + clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, name); + goto done; + } + if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ + clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); + goto done; + } + if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ + clicon_err(OE_YANG, errno, "parsing cv"); + goto done; + } + if (cvret == 0){ /* parsing failed */ + clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); + goto done; + } + free(extra); + break; default: break; } + ok: retval = 0; done: + if (prefix) + free(prefix); return retval; } diff --git a/lib/src/clixon_yang_parse.h b/lib/src/clixon_yang_parse.h index 99ce0b5d..0b9b1ec4 100644 --- a/lib/src/clixon_yang_parse.h +++ b/lib/src/clixon_yang_parse.h @@ -34,8 +34,9 @@ ***** END LICENSE BLOCK ***** - * Database specification parser cli syntax - * (Cloned from cligen parser) + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ #ifndef _CLIXON_YANG_PARSE_H_ #define _CLIXON_YANG_PARSE_H_ diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index eb235115..8c65cbd1 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -1,8 +1,4 @@ /* - * Yang 1.0 parser according to RFC6020. - * It is hopefully useful but not complete - * RFC7950 defines Yang version 1.1 - * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren @@ -34,8 +30,9 @@ ***** END LICENSE BLOCK ***** - * Database specification parser cli syntax - * (Cloned from cligen parser) + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ %{ @@ -101,12 +98,13 @@ clixon_yang_parsewrap(void) %s ESCAPE %s COMMENT1 %s COMMENT2 +%s UNKNOWN %% /* Common tokens */ [ \t] -<> { return MY_EOF; } -\n { _YY->yy_linenum++; } +<> { return MY_EOF; } +\n { _YY->yy_linenum++; } "/*" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT1); } "//" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT2); } @@ -182,7 +180,14 @@ clixon_yang_parsewrap(void) : { return *yytext; } ; { return *yytext; } . { clixon_yang_parselval.string = strdup(yytext); - return CHAR;} + BEGIN(UNKNOWN); return CHAR; } + +: { return *yytext; } +; { BEGIN(KEYWORD); return *yytext; } +[ \t] { return ' '; } +. { clixon_yang_parselval.string = strdup(yytext); + return CHAR; } + ; { BEGIN(KEYWORD); return *yytext; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 68748c8a..eb48253f 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -1,7 +1,4 @@ /* - * Yang 1.0 parser according to RFC6020. - * It is hopefully useful but not complete - * RFC7950 defines Yang version 1.1 * ***** BEGIN LICENSE BLOCK ***** @@ -34,6 +31,9 @@ ***** END LICENSE BLOCK ***** + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ @@ -46,7 +46,6 @@ %token MY_EOF %token DQ /* Double quote: " */ -%token K_UNKNOWN /* template for error */ %token CHAR %type ustring @@ -239,12 +238,18 @@ ystack_push(struct clicon_yang_yacc_arg *yy, /*! Add a yang statement to existing top-of-stack. * - * Note: consumes 'argument' which assumes it is malloced and not freed by caller + * @param[in] yy Yang yacc argument + * @param[in] keyword Yang keyword + * @param[in] argument Yang argument + * @param[in] extra Yang extra for cornercases (unknown/extension) + + * @note consumes 'argument' and 'extra' which assumes it is malloced and not freed by caller */ static yang_stmt * ysp_add(struct clicon_yang_yacc_arg *yy, enum rfc_6020 keyword, - char *argument) + char *argument, + char *extra) { struct ys_stack *ystack = yy->yy_stack; yang_stmt *ys = NULL; @@ -262,7 +267,7 @@ ysp_add(struct clicon_yang_yacc_arg *yy, ys->ys_argument = argument; if (yn_insert(yn, ys) < 0) /* Insert into hierarchy */ goto err; - if (ys_parse_sub(ys) < 0) /* Check statement-specific syntax */ + if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */ goto err2; /* dont free since part of tree */ // done: return ys; @@ -276,12 +281,12 @@ ysp_add(struct clicon_yang_yacc_arg *yy, /*! combination of ysp_add and ysp_push for sub-modules */ static yang_stmt * ysp_add_push(struct clicon_yang_yacc_arg *yy, - int keyword, + enum rfc_6020 keyword, char *argument) { yang_stmt *ys; - if ((ys = ysp_add(yy, keyword, argument)) == NULL) + if ((ys = ysp_add(yy, keyword, argument, NULL)) == NULL) return NULL; if (ystack_push(yy, (yang_node*)ys) == NULL) return NULL; @@ -330,12 +335,22 @@ file : module_stmt MY_EOF { clicon_debug(2,"file->submodule-stmt"); YYACCEPT; } ; -unknown_stmt : K_UNKNOWN { clixon_yang_parseerror(_yy, "unknown statement");clicon_debug(2,"unknown-stmt"); _YYERROR("0"); } - ; + /* For extensions */ +unknown_stmt : ustring ':' ustring ';' + { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("0"); + clicon_debug(2,"unknown-stmt -> ustring : ustring"); + } + | ustring ':' ustring ' ' string ';' + { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("0"); + clicon_debug(2,"unknown-stmt -> ustring : ustring string"); + } + ; /* module */ module_stmt : K_MODULE id_arg_str - { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); + { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); } '{' module_substmts '}' { if (ystack_pop(_yy) < 0) _YYERROR("2"); @@ -359,7 +374,7 @@ module_substmt : module_header_stmts { clicon_debug(2,"module-substmt -> module- /* submodule */ submodule_stmt : K_SUBMODULE id_arg_str '{' submodule_substmts '}' - { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); + { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); clicon_debug(2,"submodule -> id-arg-str { submodule-stmts }"); } ; @@ -422,7 +437,7 @@ revision_stmts : revision_stmts revision_stmt ; revision_stmt : K_REVISION string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION, $2) == NULL) _YYERROR("4"); + { if (ysp_add(_yy, Y_REVISION, $2, NULL) == NULL) _YYERROR("4"); clicon_debug(2,"revision-stmt -> date-arg-str ;"); } | K_REVISION string { if (ysp_add_push(_yy, Y_REVISION, $2) == NULL) _YYERROR("5"); } @@ -482,7 +497,7 @@ data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> containe /* container */ container_stmt : K_CONTAINER id_arg_str ';' - { if (ysp_add(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("7"); + { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("7"); clicon_debug(2,"container-stmt -> CONTAINER id-arg-str ;");} | K_CONTAINER id_arg_str { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("8"); } @@ -512,7 +527,7 @@ container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when- /* leaf */ leaf_stmt : K_LEAF id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF, $2) == NULL) _YYERROR("10"); + { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("10"); clicon_debug(2,"leaf-stmt -> LEAF id-arg-str ;");} | K_LEAF id_arg_str { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("11"); } @@ -542,7 +557,7 @@ leaf_substmt : when_stmt { clicon_debug(2,"leaf-substmt -> when-stmt /* leaf-list */ leaf_list_stmt : K_LEAF_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("13"); + { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("13"); clicon_debug(2,"leaf-list-stmt -> LEAF id-arg-str ;");} | K_LEAF_LIST id_arg_str { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("14"); } @@ -573,7 +588,7 @@ leaf_list_substmt : when_stmt { clicon_debug(2,"leaf-list-substmt -> when /* list */ list_stmt : K_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LIST, $2) == NULL) _YYERROR("16"); + { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("16"); clicon_debug(2,"list-stmt -> LIST id-arg-str ;"); } | K_LIST id_arg_str { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("17"); } @@ -609,7 +624,7 @@ list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt /* choice */ choice_stmt : K_CHOICE id_arg_str ';' - { if (ysp_add(_yy, Y_CHOICE, $2) == NULL) _YYERROR("19"); + { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("19"); clicon_debug(2,"choice-stmt -> CHOICE id-arg-str ;"); } | K_CHOICE id_arg_str { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("20"); } @@ -648,7 +663,7 @@ short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> conta /* case */ case_stmt : K_CASE id_arg_str ';' - { if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("22"); + { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("22"); clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } | K_CASE id_arg_str { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } @@ -676,7 +691,7 @@ case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt /* anyxml */ anyxml_stmt : K_ANYXML id_arg_str ';' - { if (ysp_add(_yy, Y_ANYXML, $2) == NULL) _YYERROR("25"); + { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("25"); clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } | K_ANYXML id_arg_str { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } @@ -704,7 +719,7 @@ anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-st /* uses */ uses_stmt : K_USES identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("28"); + { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("28"); clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } | K_USES identifier_ref_arg_str { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } @@ -732,7 +747,7 @@ uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt /* refine XXX need further refining */ refine_stmt : K_REFINE id_arg_str ';' - { if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("31"); + { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("31"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } | K_REFINE id_arg_str { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } @@ -782,7 +797,7 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s /* when */ when_stmt : K_WHEN string ';' - { if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("36"); + { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("36"); clicon_debug(2,"when-stmt -> WHEN string ;"); } | K_WHEN string { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } @@ -804,7 +819,7 @@ when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-s /* rpc */ rpc_stmt : K_RPC id_arg_str ';' - { if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("39"); + { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("39"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } | K_RPC id_arg_str { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } @@ -885,7 +900,7 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s /* Type */ type_stmt : K_TYPE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("48"); + { if (ysp_add(_yy, Y_TYPE, $2, NULL) == NULL) _YYERROR("48"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");} | K_TYPE identifier_ref_arg_str { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49"); @@ -953,7 +968,7 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st /* length-stmt */ length_stmt : K_LENGTH string ';' /* XXX length-arg-str */ - { if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("53"); + { if (ysp_add(_yy, Y_LENGTH, $2, NULL) == NULL) _YYERROR("53"); clicon_debug(2,"length-stmt -> LENGTH string ;"); } | K_LENGTH string @@ -978,7 +993,7 @@ length_substmt : error_message_stmt { clicon_debug(2,"length-substmt -> error-m /* Pattern */ pattern_stmt : K_PATTERN string ';' - { if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("56"); + { if (ysp_add(_yy, Y_PATTERN, $2, NULL) == NULL) _YYERROR("56"); clicon_debug(2,"pattern-stmt -> PATTERN string ;"); } | K_PATTERN string @@ -1002,7 +1017,7 @@ pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> refere /* Extension */ extension_stmt: K_EXTENSION id_arg_str ';' - { if (ysp_add(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("59"); + { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("59"); clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } | K_EXTENSION id_arg_str { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } @@ -1027,13 +1042,13 @@ extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argu | { clicon_debug(2,"extension-substmt -> "); } ; -argument_stmt : K_ARGUMENT id_arg_str ';' - | K_ARGUMENT id_arg_str '{' '}' +argument_stmt : K_ARGUMENT id_arg_str ';' { free($2); } + | K_ARGUMENT id_arg_str '{' '}' { free($2); } ; /* Feature */ feature_stmt : K_FEATURE id_arg_str ';' - { if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("62"); + { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("62"); clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } | K_FEATURE id_arg_str { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } @@ -1059,7 +1074,7 @@ feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-fea /* Identity */ identity_stmt : K_IDENTITY string ';' /* XXX identifier-arg-str */ - { if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("65"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("65"); clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } | K_IDENTITY string @@ -1085,7 +1100,7 @@ identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base- /* range-stmt */ range_stmt : K_RANGE string ';' /* XXX range-arg-str */ - { if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("68"); + { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("68"); clicon_debug(2,"range-stmt -> RANGE string ;"); } | K_RANGE string @@ -1110,7 +1125,7 @@ range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-me /* enum-stmt */ enum_stmt : K_ENUM string ';' - { if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("71"); + { if (ysp_add(_yy, Y_ENUM, $2, NULL) == NULL) _YYERROR("71"); clicon_debug(2,"enum-stmt -> ENUM string ;"); } | K_ENUM string { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("72"); } @@ -1135,7 +1150,7 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm /* bit-stmt */ bit_stmt : K_BIT string ';' - { if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("74"); + { if (ysp_add(_yy, Y_BIT, $2, NULL) == NULL) _YYERROR("74"); clicon_debug(2,"bit-stmt -> BIT string ;"); } | K_BIT string { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); } @@ -1159,7 +1174,7 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition /* mus-stmt */ must_stmt : K_MUST string ';' - { if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("77"); + { if (ysp_add(_yy, Y_MUST, $2, NULL) == NULL) _YYERROR("77"); clicon_debug(2,"must-stmt -> MUST string ;"); } | K_MUST string @@ -1183,7 +1198,7 @@ must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-mes /* error-message-stmt */ error_message_stmt : K_ERROR_MESSAGE string ';' - { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("80"); } + { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("80"); } /* import */ import_stmt : K_IMPORT id_arg_str @@ -1206,144 +1221,144 @@ import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } /* Simple statements */ yang_version_stmt : K_YANG_VERSION string ';' /* XXX yang-version-arg-str */ - { if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("83"); + { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("83"); clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } ; fraction_digits_stmt : K_FRACTION_DIGITS string ';' /* XXX: fraction-digits-arg-str */ - { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("84"); + { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("84"); clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } ; if_feature_stmt : K_IF_FEATURE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("85"); + { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("85"); clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } ; value_stmt : K_VALUE integer_value ';' - { if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("86"); + { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("86"); clicon_debug(2,"value-stmt -> VALUE integer-value"); } ; position_stmt : K_POSITION integer_value ';' - { if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("87"); + { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("87"); clicon_debug(2,"position-stmt -> POSITION integer-value"); } ; status_stmt : K_STATUS string ';' /* XXX: status-arg-str */ - { if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("88"); + { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("88"); clicon_debug(2,"status-stmt -> STATUS string"); } ; config_stmt : K_CONFIG config_arg_str ';' - { if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("89"); + { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("89"); clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } ; base_stmt : K_BASE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("90"); + { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("90"); clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } ; path_stmt : K_PATH string ';' /* XXX: path-arg-str */ - { if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("91"); + { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("91"); clicon_debug(2,"path-stmt -> PATH string"); } ; require_instance_stmt : K_REQUIRE_INSTANCE string ';' /* XXX: require-instance-arg-str */ - { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("92"); + { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("92"); clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } ; units_stmt : K_UNITS string ';' - { if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("93"); + { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("93"); clicon_debug(2,"units-stmt -> UNITS string"); } ; default_stmt : K_DEFAULT string ';' - { if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("94"); + { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("94"); clicon_debug(2,"default-stmt -> DEFAULT string"); } ; contact_stmt : K_CONTACT string ';' - { if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("95"); + { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("95"); clicon_debug(2,"contact-stmt -> CONTACT string"); } ; revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("96"); + { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("96"); clicon_debug(2,"revision-date-stmt -> date;"); } ; include_stmt : K_INCLUDE id_arg_str ';' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("97"); + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("97"); clicon_debug(2,"include-stmt -> id-arg-str"); } | K_INCLUDE id_arg_str '{' revision_date_stmt '}' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("98"); + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("98"); clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } ; namespace_stmt : K_NAMESPACE string ';' /* XXX uri-str */ - { if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("99"); + { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("99"); clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } ; prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */ - { if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("100"); + { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("100"); clicon_debug(2,"prefix-stmt -> PREFIX string ;");} ; description_stmt: K_DESCRIPTION string ';' - { if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("101"); + { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101"); clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} ; organization_stmt: K_ORGANIZATION string ';' - { if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("102"); + { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("102"); clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} ; min_elements_stmt: K_MIN_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("103"); + { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2, NULL)== NULL) _YYERROR("103"); clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");} ; max_elements_stmt: K_MAX_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("104"); + { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2, NULL)== NULL) _YYERROR("104"); clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");} ; reference_stmt: K_REFERENCE string ';' - { if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("105"); + { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("105"); clicon_debug(2,"reference-stmt -> REFERENCE string ;");} ; mandatory_stmt: K_MANDATORY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("106"); + if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("106"); clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} ; presence_stmt: K_PRESENCE string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("107"); + if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("107"); clicon_debug(2,"presence-stmt -> PRESENCE string ;");} ; ordered_by_stmt: K_ORDERED_BY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("108"); + if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("108"); clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} ; key_stmt : K_KEY id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("109"); + { if (ysp_add(_yy, Y_KEY, $2, NULL)== NULL) _YYERROR("109"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; unique_stmt : K_UNIQUE id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("110"); + { if (ysp_add(_yy, Y_UNIQUE, $2, NULL)== NULL) _YYERROR("110"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; @@ -1388,7 +1403,7 @@ qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"" ustring : ustring CHAR { int len = strlen($1); - $$ = realloc($1, len+strlen($2) + 1); + $$ = realloc($1, len+strlen($2) + 1); sprintf($$+len, "%s", $2); free($2); } diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 335e0ed6..1d7f57d4 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -63,6 +63,7 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_hash.h" +#include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_yang.h" @@ -905,8 +906,8 @@ yang_type_resolve(yang_stmt *ys, if (options) *options = 0x0; *yrestype = NULL; /* Initialization of resolved type that may not be necessary */ - type = ytype_id(ytype); /* This is the type to resolve */ - prefix = ytype_prefix(ytype); /* And this its prefix */ + type = yarg_id(ytype); /* This is the type to resolve */ + prefix = yarg_prefix(ytype); /* And this its prefix */ /* Cache does not work for eg string length 32 */ if (!yang_builtin(type) && ytype->ys_typecache != NULL){ if (yang_type_cache_get(ytype->ys_typecache, @@ -1034,7 +1035,7 @@ yang_type_get(yang_stmt *ys, goto done; } /* XXX: here we seem to have some problems if type is union */ - type = ytype_id(ytype); + type = yarg_id(ytype); if (origtype) *origtype = type; if (yang_type_resolve(ys, ytype, yrestype, diff --git a/test/README.md b/test/README.md index 7598af48..08c92f1a 100644 --- a/test/README.md +++ b/test/README.md @@ -4,6 +4,8 @@ This directory contains testing code for clixon and the example routing application. Assumes setup of http daemon as describe under apps/restonf - clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script - all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. +- test_auth.sh Auth tests using internal NACM +- test_auth_ext.sh Auth tests using external NACM - test_cli.sh CLI tests - test_netconf.sh Netconf tests - test_restconf.sh Restconf tests diff --git a/test/clixon b/test/clixon deleted file mode 100755 index 9c38654a..00000000 --- a/test/clixon +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh -# Top-level cron scripts. Add this to (for example) /etc/cron.daily - -err(){ - testname=$1 - errcode=$2 - echo "Error in [$testname]" - logger "CLIXON: Error in [$testname]" - exit $errcode -} - -# cd to working dir -cd /var/tmp -if [ $# -ne 0 ]; then - err "usage: $0" 0 -fi -rm -rf cligen -rm -rf clixon -git clone https://github.com/olofhagsand/cligen.git -if [ $? -ne 0 ]; then - err "git clone cligen" 1 -fi -cd cligen -CFLAGS=-Werror ./configure -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -cd .. -git clone https://github.com/clicon/clixon.git -if [ $? -ne 0 ]; then - err "git clone clixon" 1 -fi -cd clixon -CFLAGS=-Werror ./configure --with-cligen=../cligen -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install" 4 -fi -sudo make install-include -if [ $? -ne 0 ]; then - err "make install include" 5 - exit 1 -fi -cd example -make -if [ $? -ne 0 ]; then - err "make example" 6 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install example" 7 -fi -cd ../test -#./all.sh -(cd /home/olof/src/clixon/test; ./all.sh) -errcode=$? -if [ $errcode -ne 0 ]; then - err "test" $errcode -fi -cd ../.. -rm -rf clixon cligen -logger "CLIXON: tests OK" diff --git a/test/lib.sh b/test/lib.sh index 2872126e..061730f6 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -11,13 +11,14 @@ clixon_cli=clixon_cli # For memcheck / performance #clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" -# clixon_netconf="valgrind --tool=callgrind clixon_netconf +#clixon_netconf="valgrind --tool=callgrind clixon_netconf" clixon_netconf=clixon_netconf # How to run restconf stand-alone and using valgrind #sudo su -c "/www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data #sudo su -c "valgrind --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data +#clixon_backend="valgrind --leak-check=full --show-leak-kinds=all clixon_backend" clixon_backend=clixon_backend dir=/var/tmp/$0 @@ -28,13 +29,17 @@ rm -rf $dir/* # error and exit, arg is optional extra errmsg err(){ - echo "Error in Test$testnr [$testname]:" + echo -e "\e[31m\nError in Test$testnr [$testname]:" if [ $# -gt 0 ]; then echo "Expected: $1" fi if [ $# -gt 1 ]; then echo "Received: $2" fi + echo -e "\e[0m:" + echo "$ret"| od -t c > $dir/clixon-ret + echo "$expect"| od -t c > $dir/clixon-expect + diff $dir/clixon-ret $dir/clixon-expect exit $testnr } @@ -43,22 +48,28 @@ new(){ testnr=`expr $testnr + 1` testname=$1 >&2 echo "Test$testnr [$1]" -# sleep 1 +} +new2(){ + testnr=`expr $testnr + 1` + testname=$1 + >&2 echo -n "Test$testnr [$1]" } # clixon tester. First arg is command and second is expected outcome expectfn(){ cmd=$1 expect=$2 + if [ $# = 3 ]; then expect2=$3 else expect2= fi - ret=`$cmd` - if [ $? -ne 0 ]; then - err "wrong args" - fi + ret=$($cmd) + +# if [ $? -ne 0 ]; then +# err "wrong args" +# fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return @@ -68,9 +79,7 @@ expectfn(){ fi # grep extended grep match=`echo $ret | grep -EZo "$expect"` -# echo "ret:\"$ret\"" -# echo "expect:\"$expect\"" -# echo "match:\"$match\"" + if [ -z "$match" ]; then err "$expect" "$ret" fi @@ -82,6 +91,19 @@ expectfn(){ fi } +expecteq(){ + ret=$1 + expect=$2 + if [ -z "$ret" -a -z "$expect" ]; then + return + fi + if [[ "$ret" = "$expect" ]]; then + echo + else + err "$expect" "$ret" + fi +} + # clixon tester. First arg is command second is stdin and # third is expected outcome expecteof(){ diff --git a/test/test_auth.sh b/test/test_auth.sh new file mode 100755 index 00000000..225a3ba7 --- /dev/null +++ b/test/test_auth.sh @@ -0,0 +1,208 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# See RFC 8321 A.2 +# But replaced ietf-netconf-monitoring with * + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + false + internal + +EOF + +cat < $fyang +module $APPNAME{ + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +RULES=$(cat < + false + deny + deny + deny + + + admin + admin + adm1 + olof + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + + + guest-acl + guest + + deny-ncm + * + * + deny + + Do not allow guests any access to any information. + + + + + limited-acl + limited + + permit-get + get + * + exec + permit + + Allow get + + + + permit-get-config + get-config + * + exec + permit + + Allow get-config + + + + + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + 0 +EOF +) + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +sleep 1 +new "start restconf daemon (-a is enable basic authentication)" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a + +sleep 1 + +new "restconf DELETE whole datastore" +expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" + +new2 "auth get" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} + ' + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" + +new2 "auth get (no user: access denied)" +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (wrong passwd: access denied)" +expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (access)" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +#----------------Enable NACM + +new "enable nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +new2 "admin get nacm" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "limited get nacm" +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "guest get nacm" +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new "admin edit nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" + +new2 "limited edit nacm" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new2 "guest edit nacm" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new "Kill restconf daemon" +sudo pkill -u www-data clixon_restconf + +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh new file mode 100755 index 00000000..373fca1f --- /dev/null +++ b/test/test_auth_ext.sh @@ -0,0 +1,219 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# External NACM file +# See RFC 8321 A.2 +# But replaced ietf-netconf-monitoring with * + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang +nacmfile=$dir/nacmfile + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + false + external + $nacmfile + +EOF + +cat < $fyang +module $APPNAME{ + prefix ex; + container authentication { + description "Example code for enabling www basic auth and some example + users"; + leaf basic_auth{ + description "Basic user / password authentication as in HTTP basic auth"; + type boolean; + default true; + } + list auth { + description "user / password entries. Valid if basic_auth=true"; + key user; + leaf user{ + description "User name"; + type string; + } + leaf password{ + description "Password"; + type string; + } + } + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +cat < $nacmfile + + true + deny + deny + deny + + + admin + admin + adm1 + olof + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + + + guest-acl + guest + + deny-ncm + * + * + deny + + Do not allow guests any access to any information. + + + + + limited-acl + limited + + permit-get + get + * + exec + permit + + Allow get + + + + permit-get-config + get-config + * + exec + permit + + Allow get-config + + + + + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + +EOF + +# kill old backend (if any) +new "kill old backend -zf $cfg -y $fyang" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +sleep 1 +new "start restconf daemon (-a is enable http basic auth)" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a + +sleep 1 + +new "restconf DELETE whole datastore" +expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" + +new2 "auth get" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} + ' + +new "Set x to 0" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" + +new2 "auth get (no user: access denied)" +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (wrong passwd: access denied)" +expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (access)" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "admin get nacm" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "limited get nacm" +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "guest get nacm" +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new "admin edit nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" + +new2 "limited edit nacm" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new2 "guest edit nacm" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new "Kill restconf daemon" +sudo pkill -u www-data clixon_restconf + +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_cli.sh b/test/test_cli.sh index 496b9d95..ec71a707 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -8,6 +8,7 @@ # Set the mandatory type # Commit +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -15,15 +16,15 @@ cfg=$dir/conf_yang.xml cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF @@ -57,7 +58,12 @@ new "cli configure" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" "^$" new "cli show configuration" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" +expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface eth/0/0 enabled true" + +new "cli configure using encoded chars data <&" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" "" +new "cli configure using encoded chars name <&" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface fddi&< type eth" "" new "cli failed validate" expectfn "$clixon_cli -1 -f $cfg -l o validate" "Missing mandatory variable" @@ -95,7 +101,8 @@ new "cli load" expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" "^$" new "cli check load" -expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" +expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" "^interfaces interface name eth/0/0 type bgp +interfaces interface eth/0/0 ipv4 enabled true" new "cli debug" expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" "^$" diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 6f7ae875..b0714b4d 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -55,7 +55,7 @@ run(){ rm -rf $mydir/* conf="-d candidate -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip" - echo "conf:$conf" + new "datastore $name init" expectfn "$datastore $conf init" "" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index bb5649ff..1b7c2990 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -1,6 +1,6 @@ #!/bin/bash # Test7: Yang specifics: leafref - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -9,15 +9,15 @@ fyang=$dir/leafref.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF @@ -88,8 +88,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth3
10.0.4.6
]]>]]>" "^]]>]]>$" -new "leafref validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^missing-attribute" +new "leafref validate XXX shouldnt really be operation-failed, more work in validate code" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" @@ -109,7 +109,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^missing-attribute" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 78070864..b6fc58ac 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -1,155 +1,202 @@ #!/bin/bash # Test2: backend and netconf basic functionality - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml +fyang=$dir/netconf.yang cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $fyang + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF -echo "clixon_backend -zf $cfg" +cat < $fyang +module example{ + prefix ex; + import ietf-ip { + prefix ip; + } + import ietf-routing { + prefix rt; + } + import ietf-inet-types { + prefix "inet"; + revision-date "2013-07-15"; + } + rpc empty { + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } +} +EOF + # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi -new "start backend -s init -f $cfg" +new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg +sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi -new "netconf tests" - -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +new "netconf get-config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg" "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "noneeth/0/0]]>]]>" "^]]>]]>$" new "Check nothing added" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth/0/0ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0ethtrue]]>]]>$" new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" new "Check deleted eth/0/0 (non-presence container)" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg" "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth1true]]>]]>$" new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" new "netconf validate missing type" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get empty config2" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg" "eth1eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" -new "netconf edit config replace XXX is merge?" -expecteof "$clixon_netconf -qf $cfg" "eth2ethmerge]]>]]>" "^]]>]]>$" +new "netconf edit config merge" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth2ethmerge]]>]]>" "^]]>]]>$" + +new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth& t< > ]]>]]>" "^]]>]]>$" new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth1ethtrueeth2ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" + +new "cli show configuration eth& - encoding tests" +expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" "interfaces interface eth& type t<> +interfaces interface eth& enabled true" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg" "eth1eth]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^invalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth0eth42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth0eth42]]>]]>$" new "netconf lock/unlock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" new "netconf lock/lock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf lock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "close-session" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "kill-session" -expecteof "$clixon_netconf -qf $cfg" "44]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "44]]>]]>" "^]]>]]>$" new "copy startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth1ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth1ethtrue]]>]]>$" new "netconf delete startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf check empty startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" new "netconf rpc w/o namespace" -expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" + +new "netconf empty rpc" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" + +new "netconf client-side rpc" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "example]]>]]>" "^ok]]>]]>$" new "netconf subscription" -expectwait "$clixon_netconf -qf $cfg" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 +expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 new "Kill backend" # Check if still alive diff --git a/test/test_order.sh b/test/test_order.sh index d29ad4f2..a046d5c6 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -5,7 +5,7 @@ # The ordered-by user MUST be the order it is entered. # No test of ordered-by system is done yet # (we may want to sort them alphabetically for better performance). - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -25,13 +25,13 @@ fi cat < $cfg /tmp/conf_yang.xml - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 $dbdir /usr/local/lib/xmldb/text.so diff --git a/test/test_perf.sh b/test/test_perf.sh index ece6a3b6..8ad933e1 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -14,7 +14,7 @@ else echo "Usage: $0 [ []]" exit 1 fi - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -46,10 +46,10 @@ cat < $cfg $cfg $fyang ietf-ip - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile false - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF @@ -72,7 +72,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df $cfg -y $fyang +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D sleep 1 diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c1a25263..344b521f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -1,7 +1,7 @@ #!/bin/bash # Restconf basic functionality # Assume http server setup, such as nginx described in apps/restconf/README.md - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf.xml @@ -11,18 +11,19 @@ fyang=$dir/restconf.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang $fyang - /usr/local/lib/routing/clispec - /usr/local/lib/routing/backend - /usr/local/lib/routing/restconf + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + example_backend.so$ + /usr/local/lib/$APPNAME/restconf false - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF @@ -50,6 +51,19 @@ module example{ output { } } + rpc client-rpc { + description "Example local client-side rpc"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } EOF @@ -63,7 +77,7 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo clixon_backend -s init -f $cfg -y $fyang +sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi @@ -72,33 +86,62 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D sleep 1 new "restconf tests" -new "restconf options" -expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" +new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" +expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " + + " -new "restconf head" +new2 "restconf get restconf resource. RFC 8040 3.3 (json)" +expecteq "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}} + ' + +new2 "restconf get restconf resource. RFC 8040 3.3 (xml)" +# Get XML instead of JSON? +expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 + ' + +new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} + ' + +new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) +expect="" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new2 "restconf get restconf/yang-library-version. RFC8040 3.3.3" +expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}' + +new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) +expect="2016-06-21" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf options. RFC 8040 4.1" +expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" + +new "restconf head. RFC 8040 4.2" expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" -new "restconf root discovery" -expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "" +new2 "restconf empty rpc" +expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" "" -new "restconf get restconf json" -expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' - -new "restconf get restconf/yang-library-version json" -expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}' - -new "restconf empty rpc" -expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}' - -new "restconf get empty config + state json" -expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" +new2 "restconf get empty config + state json" +expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) @@ -108,8 +151,9 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get data/interfaces-state/interface=eth0 json" -expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0" '{"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}' +new2 "restconf get data/interfaces-state/interface=eth0 json" +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]} + ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh @@ -120,9 +164,9 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get state operation eth0 type json" -expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type" '{"type": "eth"} -$' +new2 "restconf get state operation eth0 type json" +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"} + ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh @@ -133,60 +177,91 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' "" +new2 "restconf GET datastore" +expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' + +# Exact match +new "restconf Add subtree to datastore using POST" +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' 'HTTP/1.1 200 OK' + +new "restconf Re-add subtree which should give error" +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' + +# XXX Cant get this to work +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} + ' -new "restconf delete interfaces" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces' "" +new2 "restconf delete interfaces" +expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data" $state +expectfn "curl -sG http://localhost/restconf/data" "$state" new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "" +# XXX cant get this to work +#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" -new "restconf Check eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +new2 "restconf Check eth/0/0 added" +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' -new "restconf Re-post eth/0/0 which should generate error" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "Data resource already exists" +new2 "restconf Re-post eth/0/0 which should generate error" +expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "Add leaf description using POST" -expectfn 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "data is in some way badly formed" +expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' -new "restconf Check description added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}\]} -$' +new2 "restconf Check description added" +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' new "restconf delete eth/0/0" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' $state -new "restconf Re-Delete eth/0/0 using none should generate error" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" +new2 "restconf Re-Delete eth/0/0 using none should generate error" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' new "restconf Add subtree eth/0/0 using PUT" -expectfn 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" -new "restconf get subtree" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +new2 "restconf get subtree" +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' -new "restconf rpc using POST json" -expectfn 'curl -s -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}' +new2 "restconf rpc using POST json" +expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} + ' + +# Cant get this to work due to quoting +#new2 "restconf rpc using POST wrong JSON" +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' + +new2 "restconf rpc using POST json w/o mandatory element" +expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' +new2 "restconf rpc non-existing rpc w/o namespace" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' + +new2 "restconf rpc non-existing rpc" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ex:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' + +new2 "restconf rpc missing name" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' + +new2 "restconf rpc missing input" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' new "restconf rpc using POST xml" -# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) expect="ipv42.3.4.5" match=`echo $ret | grep -EZo "$expect"` @@ -194,6 +269,20 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +new "restconf local client rpc using POST xml" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) +expect="ok" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +# XXX cant get -H to work +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5' + +# Cant get shell macros to work, inline matching from lib.sh + + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index f0ae519e..df401e24 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -1,7 +1,7 @@ #!/bin/bash # Restconf basic functionality # Assume http server setup, such as nginx described in apps/restconf/README.md - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf.xml @@ -13,19 +13,17 @@ cat < $cfg $cfg /usr/local/var $fyang - false - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - 1 - /usr/local/var/routing + /usr/local/var/$APPNAME/$APPNAME.sock + $dir/restconf.pidfile + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so
EOF cat < $fyang module example{ - container interfaces-config{ + container cont1{ list interface{ key name; leaf name{ @@ -37,6 +35,11 @@ module example{ } } } + container cont2{ + leaf name{ + type string; + } + } } EOF @@ -56,62 +59,82 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df $cfg +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D sleep 1 new "restconf tests" new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0/type" '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" '{"type": "regular"}' + +new "restconf POST interface without mandatory type" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' '"error-message": "Missing mandatory variable: type"' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' "" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" -new "restconf POST again" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' "Data resource already exis" +new2 "restconf POST again" +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' -new "restconf POST from top" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists" +new2 "restconf POST from top" +expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces-config' "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" new "restconf GET null datastore" expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" + +new "restconf GET initial tree" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' + +new "restconf DELETE whole datastore" +expectfn 'curl -s -X DELETE http://localhost/restconf/data' "" + +new "restconf GET null datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"interfaces-config":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' + +new "restconf PUT replace datastore" +expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' "" + +new "restconf GET replaced datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont2": {"name": "foo"}}}' + + +new "restconf PUT initial datastore again" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/interfaces-config/interface=local0' "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' "" + new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' "Bad request" - -new "restconf POST invalid no type" -expectfn 'curl -s -X POST -d {"interface":{"name":"ALPHA"}} http://localhost/restconf/data/interfaces-config' "Bad request" +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_startup.sh b/test/test_startup.sh index c81f88b9..d7b5d6a8 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -5,7 +5,7 @@ # - An extra xml configuration file starts with an "extra" interface # - running db starts with a "run" interface # - startup db starts with a "start" interface - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_startup.xml @@ -13,18 +13,18 @@ cfg=$dir/conf_startup.xml cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - routing - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - /usr/local/lib/routing/clispec - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so 0 init @@ -48,7 +48,7 @@ run(){ EOF - sudo mv $dbdir /usr/local/var/routing/running_db + sudo mv $dbdir /usr/local/var/$APPNAME/running_db cat < $dbdir @@ -60,7 +60,7 @@ EOF EOF - sudo mv $dbdir /usr/local/var/routing/startup_db + sudo mv $dbdir /usr/local/var/$APPNAME/startup_db cat < $dir/config diff --git a/test/test_type.sh b/test/test_type.sh index dcc2288b..7788ee02 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,24 +1,25 @@ #!/bin/bash # Advanced union types and generated code # and enum w values - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh -fyang=$dir/type.yang + cfg=$dir/conf_yang.xml +fyang=$dir/type.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF @@ -70,6 +71,57 @@ module example{ enum down; } } + leaf length1 { + type string { + length "1"; + } + } +/* leaf length2 { + type string { + length "max"; + } + } + leaf length3 { + type string { + length "min"; + } + }*/ + leaf length4 { + type string { + length "4..4000"; + } + } +/* leaf length5 { + type string { + length "min..max"; + } + }*/ + leaf num1 { + type int32 { + range "1"; + } + } +/* leaf num2 { + type int32 { + range "min"; + } + } + leaf num3 { + type int32 { + range "max"; + } + } +*/ + leaf num4 { + type int32 { + range "4..4000"; + } + } +/* leaf num5 { + type int32 { + range "min..max"; + } + }*/ } EOF @@ -111,7 +163,7 @@ new "netconf validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "a.b&c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "a.b& c.d]]>]]>" "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" diff --git a/test/test_yang.sh b/test/test_yang.sh index 15eeba11..52d4a70f 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -1,33 +1,40 @@ #!/bin/bash # Test4: Yang specifics: multi-keys and empty type - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang +fyangerr=$dir/err.yang cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF cat < $fyang -module example{ +module $APPNAME{ + prefix ex; + extension c-define { + description "Example from RFC 6020"; + argument "name"; + } + ex:c-define "MY_INTERFACES"; container x { list y { - key "a b"; + key "a b c"; leaf a { type string; } @@ -37,6 +44,9 @@ module example{ leaf c { type string; } + leaf val { + type string; + } } leaf d { type empty; @@ -75,6 +85,17 @@ module example{ } EOF +# This yang definition uses an extension which is not defined. Error when loading +cat < $fyangerr +module $APPNAME{ + prefix ex; + extension c-define { + description "Example from RFC 6020"; + argument "name"; + } + ex:not-defined ARGUMENT; +} +EOF # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg -y $fyang @@ -89,8 +110,15 @@ if [ $? -ne 0 ]; then err fi +new "cli defined extension" +expectfn "$clixon_cli -1f $cfg -y $fyang show version" "3." + +new "cli not defined extension" +# This text yields an error, but the test cannot detect the error message yet +#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" "Yang error: Extension ex:not-defined not found" + new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "125]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "125one]]>]]>" "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" @@ -100,7 +128,7 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" new "netconf edit leaf-list" expecteof "$clixon_netconf -qf $cfg -y $fyang" "hejhopp]]>]]>" "^]]>]]>$" @@ -112,7 +140,7 @@ new "netconf get leaf-list path" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" "" @@ -137,6 +165,34 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +new "netconf delete candidate" +expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" + +# Check 3-keys +new "netconf add one 3-key entry" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "111one]]>]]>" "^]]>]]>$" + +new "netconf check add one 3-key" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one]]>]]>' + +new "netconf add another (with same 1st key)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "121two]]>]]>" "^]]>]]>$" + +new "netconf check add another" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one121two]]>]]>' + +new "netconf replace first" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "111replace]]>]]>" "^]]>]]>$" + +new "netconf check replace" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111replace121two]]>]]>' + +new "netconf delete first" +expecteof "$clixon_netconf -qf $cfg -y $fyang" '111]]>]]>' "^]]>]]>$" + +new "netconf check delete" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '121two]]>]]>' + # Check if still alive pid=`pgrep clixon_backend` if [ -z "$pid" ]; then diff --git a/yang/Makefile.in b/yang/Makefile.in index cd3f988c..965c0655 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -40,8 +40,9 @@ datarootdir = @datarootdir@ YANGSPECS = clixon-config@2018-02-12.yang YANGSPECS += ietf-netconf@2011-06-01.yang -YANGSPECS += ietf-netconf-acm@2012-02-22.yang +YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang +YANGSPECS += ietf-yang-types@2013-07-15.yang APPNAME = clixon # subdir ehere these files are installed @@ -58,8 +59,8 @@ distclean: clean install: $(YANGSPECS) echo $(DESTDIR)$(datarootdir)/clixon/clixon.mk echo $(DESTDIR)$(clixon_DATADIR) - install -d $(DESTDIR)$(clixon_DATADIR) - install $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) + install -d -m 0755 $(DESTDIR)$(clixon_DATADIR) + install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) uninstall: (cd $(DESTDIR)$(clixon_DATADIR); rm -rf $(YANGSPECS)) diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 883a002f..c629a3c7 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -82,6 +82,40 @@ module clixon-config { } } } + typedef cli_genmodel_type{ + description + "How to generate CLI from YANG model, + eg list a{ key x; leaf x; leaf y;}"; + type enumeration{ + enum NONE{ + description "No extra keywords: a "; + } + enum VARS{ + description "Keywords on non-key variables: a y "; + } + enum ALL{ + description "Keywords on all variables: a x y "; + } + } + } + typedef nacm_mode{ + description + "Mode of RFC8341 Network Configuration Access Control Model. + It is unclear from the RFC whether NACM rules are internal + in a configuration (ie embedded in regular config) or external/OOB + in s separate, specific NACM-config"; + type enumeration{ + enum disabled{ + description "NACM is disabled"; + } + enum internal{ + description "NACM is enabled and available in the regular config"; + } + enum external{ + description "NACM is enabled and available in a separate config"; + } + } + } container config { leaf CLICON_CONFIGFILE{ type string; @@ -113,6 +147,12 @@ module clixon-config { "Location of backend .so plugins. Load all .so plugins in this dir as backend plugins"; } + leaf CLICON_BACKEND_REGEXP { + type string; + description + "Regexp of matching backend plugins in CLICON_BACKEND_DIR"; + default "(.so)$"; + } leaf CLICON_NETCONF_DIR { type string; description "Location of netconf (frontend) .so plugins"; @@ -180,7 +220,7 @@ module clixon-config { description "Generate code for CLI completion of existing db symbols"; } leaf CLICON_CLI_GENMODEL_TYPE { - type string; + type cli_genmodel_type; default "VARS"; description "How to generate and show CLI syntax: VARS|ALL"; } @@ -237,14 +277,6 @@ module clixon-config { "Set if all configuration changes are committed automatically on every edit change. Explicit commit commands unnecessary"; } - leaf CLICON_MASTER_PLUGIN { - type string; - default "master"; - description - "Name of master plugin (both frontend and backend). - Master plugin has special callbacks for frontends. - See clicon user manual for more info. (Obsolete?)"; - } leaf CLICON_XMLDB_DIR { type string; mandatory true; @@ -300,5 +332,22 @@ module clixon-config { type startup_mode; description "Which method to boot/start clicon backend"; } + leaf CLICON_TRANSACTION_MOD { + type boolean; + default false; + description "If set, modifications in validation and commit + callbacks are written back into the datastore"; + } + leaf CLICON_NACM_MODE { + type nacm_mode; + default disabled; + description "RFC8341 network access configuration control model + (NACM) mode: disabled, in regular (internal) config + or separate external file given by CLICON_NACM_FILE"; + } + leaf CLICON_NACM_FILE { + type string; + description "RFC8341 NACM external configuration file"; + } } } diff --git a/yang/ietf-netconf-acm@2012-02-22.yang b/yang/ietf-netconf-acm@2018-02-14.yang similarity index 73% rename from yang/ietf-netconf-acm@2012-02-22.yang rename to yang/ietf-netconf-acm@2018-02-14.yang index 99ad961f..d6e7d86e 100644 --- a/yang/ietf-netconf-acm@2012-02-22.yang +++ b/yang/ietf-netconf-acm@2018-02-14.yang @@ -1,47 +1,54 @@ module ietf-netconf-acm { + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; - prefix "nacm"; + + prefix nacm; + import ietf-yang-types { prefix yang; } + organization "IETF NETCONF (Network Configuration) Working Group"; contact - "WG Web: + "WG Web: WG List: - WG Chair: Mehmet Ersue - - - WG Chair: Bert Wijnen - - - Editor: Andy Bierman + Author: Andy Bierman - Editor: Martin Bjorklund + Author: Martin Bjorklund "; description - "NETCONF Access Control Model. + "Network Configuration Access Control Model. - Copyright (c) 2012 IETF Trust and the persons identified as - authors of the code. All rights reserved. + Copyright (c) 2012 - 2018 IETF Trust and the persons + identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Simplified BSD License set forth in Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents - (http://trustee.ietf.org/license-info). + (https://trustee.ietf.org/license-info). - This version of this YANG module is part of RFC 6536; see + This version of this YANG module is part of RFC 8341; see the RFC itself for full legal notices."; + revision "2018-02-14" { + description + "Added support for YANG 1.1 actions and notifications tied to + data nodes. Clarified how NACM extensions can be used by + other data models."; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + revision "2012-02-22" { description - "Initial version"; + "Initial version."; reference "RFC 6536: Network Configuration Protocol (NETCONF) Access Control Model"; @@ -56,11 +63,13 @@ module ietf-netconf-acm { "Used to indicate that the data model node represents a sensitive security system parameter. - If present, and the NACM module is enabled (i.e., - /nacm/enable-nacm object equals 'true'), the NETCONF server - will only allow the designated 'recovery session' to have - write access to the node. An explicit access control rule is - required for all other users. + If present, the NETCONF server will only allow the designated + 'recovery session' to have write access to the node. An + explicit access control rule is required for all other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. The 'default-deny-write' extension MAY appear within a data definition statement. It is ignored otherwise."; @@ -71,11 +80,14 @@ module ietf-netconf-acm { "Used to indicate that the data model node controls a very sensitive security system parameter. - If present, and the NACM module is enabled (i.e., - /nacm/enable-nacm object equals 'true'), the NETCONF server - will only allow the designated 'recovery session' to have - read, write, or execute access to the node. An explicit - access control rule is required for all other users. + If present, the NETCONF server will only allow the designated + 'recovery session' to have read, write, or execute access to + the node. An explicit access control rule is required for all + other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. The 'default-deny-all' extension MAY appear within a data definition statement, 'rpc' statement, or 'notification' @@ -91,12 +103,12 @@ module ietf-netconf-acm { length "1..max"; } description - "General Purpose Username string."; + "General-purpose username string."; } typedef matchall-string-type { type string { - pattern "\*"; + pattern '\*'; } description "The string containing a single asterisk '*' is used @@ -121,6 +133,7 @@ module ietf-netconf-acm { "Any protocol operation that alters an existing data node."; } + bit delete { description "Any protocol operation that removes a data node."; @@ -131,13 +144,13 @@ module ietf-netconf-acm { } } description - "NETCONF Access Operation."; + "Access operation."; } typedef group-name-type { type string { length "1..max"; - pattern "[^\*].*"; + pattern '[^\*].*'; } description "Name of administrative group to which @@ -164,29 +177,35 @@ module ietf-netconf-acm { type yang:xpath1.0; description "Path expression used to represent a special - data node instance identifier string. + data node, action, or notification instance-identifier + string. A node-instance-identifier value is an unrestricted YANG instance-identifier expression. - All the same rules as an instance-identifier apply - except predicates for keys are optional. If a key + + All the same rules as an instance-identifier apply, + except that predicates for keys are optional. If a key predicate is missing, then the node-instance-identifier represents all possible server instances for that key. - This XPath expression is evaluated in the following context: + This XML Path Language (XPath) expression is evaluated in the + following context: - o The set of namespace declarations are those in scope on - the leaf element where this type is used. + o The set of namespace declarations are those in scope on + the leaf element where this type is used. - o The set of variable bindings contains one variable, - 'USER', which contains the name of the user of the current - session. + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the + current session. - o The function library is the core function library, but - note that due to the syntax restrictions of an - instance-identifier, no functions are allowed. + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. - o The context node is the root node in the data tree."; + o The context node is the root node in the data tree. + + The accessible tree includes actions and notifications tied + to data nodes."; } /* @@ -194,14 +213,14 @@ module ietf-netconf-acm { */ container nacm { - /* nacm:default-deny-all; XXX How is this parsed ?? */ + nacm:default-deny-all; description - "Parameters for NETCONF Access Control Model."; + "Parameters for NETCONF access control model."; leaf enable-nacm { type boolean; - default true; + default "true"; description "Enables or disables all NETCONF access control enforcement. If 'true', then enforcement @@ -237,7 +256,7 @@ module ietf-netconf-acm { leaf enable-external-groups { type boolean; - default true; + default "true"; description "Controls whether the server uses the groups reported by the NETCONF transport layer when it assigns the user to a set of @@ -277,13 +296,13 @@ module ietf-netconf-acm { container groups { description - "NETCONF Access Control Groups."; + "NETCONF access control groups."; list group { key name; description - "One NACM Group Entry. This list will only contain + "One NACM group entry. This list will only contain configured entries, not any entries learned from any transport protocols."; @@ -304,7 +323,7 @@ module ietf-netconf-acm { } list rule-list { - key "name"; + key name; ordered-by user; description "An ordered collection of access control rules."; @@ -331,7 +350,7 @@ module ietf-netconf-acm { } list rule { - key "name"; + key name; ordered-by user; description "One access control rule. @@ -339,8 +358,8 @@ module ietf-netconf-acm { Rules are processed in user-defined order until a match is found. A rule matches if 'module-name', 'rule-type', and 'access-operations' match the request. If a rule - matches, the 'action' leaf determines if access is granted - or not."; + matches, the 'action' leaf determines whether or not + access is granted."; leaf name { type string { @@ -391,18 +410,20 @@ module ietf-netconf-acm { value equals the requested notification name."; } } + case data-node { leaf path { type node-instance-identifier; mandatory true; description - "Data Node Instance Identifier associated with the - data node controlled by this rule. + "Data node instance-identifier associated with the + data node, action, or notification controlled by + this rule. - Configuration data or state data instance - identifiers start with a top-level data node. A - complete instance identifier is required for this - type of path value. + Configuration data or state data + instance-identifiers start with a top-level + data node. A complete instance-identifier is + required for this type of path value. The special value '/' refers to all possible datastore contents."; @@ -428,7 +449,7 @@ module ietf-netconf-acm { mandatory true; description "The access control action associated with the - rule. If a rule is determined to match a + rule. If a rule has been determined to match a particular request, then this object is used to determine whether to permit or deny the request."; @@ -442,4 +463,4 @@ module ietf-netconf-acm { } } } -} \ No newline at end of file +} diff --git a/yang/ietf-yang-types@2013-07-15.yang b/yang/ietf-yang-types@2013-07-15.yang new file mode 100644 index 00000000..07834130 --- /dev/null +++ b/yang/ietf-yang-types@2013-07-15.yang @@ -0,0 +1,481 @@ + module ietf-yang-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types"; + prefix "yang"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - yang-identifier + - hex-string + - uuid + - dotted-quad"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of counter and gauge types ***/ + + typedef counter32 { + type uint32; + description + "The counter32 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter32 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter32 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter32. + + In the value set and its semantics, this type is equivalent + to the Counter32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter32 { + type yang:counter32; + default "0"; + description + "The zero-based-counter32 type represents a counter32 + that has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter32 textual convention of the SMIv2."; + reference + "RFC 4502: Remote Network Monitoring Management Information + Base Version 2"; + } + + typedef counter64 { + type uint64; + description + "The counter64 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter64 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter64 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter64. + + In the value set and its semantics, this type is equivalent + to the Counter64 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter64 { + type yang:counter64; + default "0"; + description + "The zero-based-counter64 type represents a counter64 that + has the defined 'initial' value zero. + + + + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter64 textual convention of the SMIv2."; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + typedef gauge32 { + type uint32; + description + "The gauge32 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^32-1 (4294967295 decimal), and + the minimum value cannot be smaller than 0. The value of + a gauge32 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge32 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the Gauge32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef gauge64 { + type uint64; + description + "The gauge64 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^64-1 (18446744073709551615), and + the minimum value cannot be smaller than 0. The value of + a gauge64 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge64 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the CounterBasedGauge64 SMIv2 textual convention defined + in RFC 2856"; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + /*** collection of identifier-related types ***/ + + typedef object-identifier { + type string { + pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))' + + '(\.(0|([1-9]\d*)))*'; + } + description + "The object-identifier type represents administratively + assigned names in a registration-hierarchical-name tree. + + Values of this type are denoted as a sequence of numerical + non-negative sub-identifier values. Each sub-identifier + value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers + are separated by single dots and without any intermediate + whitespace. + + The ASN.1 standard restricts the value space of the first + sub-identifier to 0, 1, or 2. Furthermore, the value space + of the second sub-identifier is restricted to the range + 0 to 39 if the first sub-identifier is 0 or 1. Finally, + the ASN.1 standard requires that an object identifier + has always at least two sub-identifiers. The pattern + captures these restrictions. + + Although the number of sub-identifiers is not limited, + module designers should realize that there may be + implementations that stick with the SMIv2 limit of 128 + sub-identifiers. + + This type is a superset of the SMIv2 OBJECT IDENTIFIER type + since it is not restricted to 128 sub-identifiers. Hence, + this type SHOULD NOT be used to represent the SMIv2 OBJECT + IDENTIFIER type; the object-identifier-128 type SHOULD be + used instead."; + reference + "ISO9834-1: Information technology -- Open Systems + Interconnection -- Procedures for the operation of OSI + Registration Authorities: General procedures and top + arcs of the ASN.1 Object Identifier tree"; + } + + typedef object-identifier-128 { + type object-identifier { + pattern '\d*(\.\d*){1,127}'; + } + description + "This type represents object-identifiers restricted to 128 + sub-identifiers. + + In the value set and its semantics, this type is equivalent + to the OBJECT IDENTIFIER type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef yang-identifier { + type string { + length "1..max"; + pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*'; + pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*'; + } + description + "A YANG identifier string as defined by the 'identifier' + rule in Section 12 of RFC 6020. An identifier must + start with an alphabetic character or an underscore + followed by an arbitrary sequence of alphabetic or + numeric characters, underscores, hyphens, or dots. + + A YANG identifier MUST NOT start with any possible + combination of the lowercase or uppercase character + sequence 'xml'."; + reference + "RFC 6020: YANG - A Data Modeling Language for the Network + Configuration Protocol (NETCONF)"; + } + + /*** collection of types related to date and time***/ + + typedef date-and-time { + type string { + pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?' + + '(Z|[\+\-]\d{2}:\d{2})'; + } + description + "The date-and-time type is a profile of the ISO 8601 + standard for representation of dates and times using the + Gregorian calendar. The profile is defined by the + date-time production in Section 5.6 of RFC 3339. + + The date-and-time type is compatible with the dateTime XML + schema type with the following notable exceptions: + + (a) The date-and-time type does not allow negative years. + + (b) The date-and-time time-offset -00:00 indicates an unknown + time zone (see RFC 3339) while -00:00 and +00:00 and Z + all represent the same time zone in dateTime. + + (c) The canonical format (see below) of data-and-time values + differs from the canonical format used by the dateTime XML + schema type, which requires all times to be in UTC using + the time-offset 'Z'. + + This type is not equivalent to the DateAndTime textual + convention of the SMIv2 since RFC 3339 uses a different + separator between full-date and full-time and provides + higher resolution of time-secfrac. + + The canonical format for date-and-time values with a known time + zone uses a numeric time zone offset that is calculated using + the device's configured known offset to UTC time. A change of + the device's offset to UTC time will cause date-and-time values + to change accordingly. Such changes might happen periodically + in case a server follows automatically daylight saving time + (DST) time zone offset changes. The canonical format for + date-and-time values with an unknown time zone (usually + referring to the notion of local time) uses the time-offset + -00:00."; + reference + "RFC 3339: Date and Time on the Internet: Timestamps + RFC 2579: Textual Conventions for SMIv2 + XSD-TYPES: XML Schema Part 2: Datatypes Second Edition"; + } + + typedef timeticks { + type uint32; + description + "The timeticks type represents a non-negative integer that + represents the time, modulo 2^32 (4294967296 decimal), in + hundredths of a second between two epochs. When a schema + node is defined that uses this type, the description of + the schema node identifies both of the reference epochs. + + In the value set and its semantics, this type is equivalent + to the TimeTicks type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef timestamp { + type yang:timeticks; + description + "The timestamp type represents the value of an associated + timeticks schema node at which a specific occurrence + happened. The specific occurrence must be defined in the + description of any schema node defined using this type. When + the specific occurrence occurred prior to the last time the + associated timeticks attribute was zero, then the timestamp + value is zero. Note that this requires all timestamp values + to be reset to zero when the value of the associated timeticks + attribute reaches 497+ days and wraps around to zero. + + The associated timeticks schema node must be specified + in the description of any schema node using this type. + + In the value set and its semantics, this type is equivalent + to the TimeStamp textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of generic address types ***/ + + typedef phys-address { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + + + + + description + "Represents media- or physical-level addresses represented + as a sequence octets, each octet represented by two hexadecimal + numbers. Octets are separated by colons. The canonical + representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the PhysAddress textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + typedef mac-address { + type string { + pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'; + } + description + "The mac-address type represents an IEEE 802 MAC address. + The canonical representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the MacAddress textual convention of the SMIv2."; + reference + "IEEE 802: IEEE Standard for Local and Metropolitan Area + Networks: Overview and Architecture + RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of XML-specific types ***/ + + typedef xpath1.0 { + type string; + description + "This type represents an XPATH 1.0 expression. + + When a schema node is defined that uses this type, the + description of the schema node MUST specify the XPath + context in which the XPath expression is evaluated."; + reference + "XPATH: XML Path Language (XPath) Version 1.0"; + } + + /*** collection of string types ***/ + + typedef hex-string { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + description + "A hexadecimal string with octets represented as hex digits + separated by colons. The canonical representation uses + lowercase characters."; + } + + typedef uuid { + type string { + pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-' + + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; + } + description + "A Universally Unique IDentifier in the string representation + defined in RFC 4122. The canonical representation uses + lowercase characters. + + The following is an example of a UUID in string representation: + f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + "; + reference + "RFC 4122: A Universally Unique IDentifier (UUID) URN + Namespace"; + } + + typedef dotted-quad { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + } + description + "An unsigned 32-bit number expressed in the dotted-quad + notation, i.e., four octets written as decimal numbers + and separated with the '.' (full stop) character."; + } + } +