diff --git a/CHANGELOG.md b/CHANGELOG.md
index f49bf034..0ea76a9f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,52 @@
# Clixon Changelog
-## 4.0.0 (Expected: 13 July 2019)
+## 4.1.0 (Expected: August 2019)
+
+### Major New features
+* Restconf RFC 8040 increased feature compliance
+ * RESTCONF "insert" and "point" query parameters supported
+ * RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
+ * `201 Created` for created resources
+ * `204 No Content` for replaced resources.
+ * identity/identityref mapped between XML and JSON
+ * XML uses prefixes, JSON uses module-names (previously prefixes were used in both cases)
+ * See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
+ * Implementation detail: due to difference between RESTCONF and NETCONF semantics, a PUT first to make en internal netconf edit-config create operation; if that fails, a replace operation is tried.
+ * HTTP `Location:` fields added in RESTCONF POST replies
+ * HTTP `Cache-Control: no-cache` fields added in HTTP responses (RFC Section 5.5)
+ * Restconf monitoring capabilities (RFC Section 9.1)
+* Yang Netconf leaf/leaf-list insert support
+ * For "ordered-by user" leafs and leaf-lists, the insert and value/key attributes are supported according to RFC7950 Sections 7.7.9 and 7.8.6
+* Yang extensions support
+ * New plugin callback: ca_extension
+ * The main example explains how to implement a Yang extension in a backend plugin.
+
+### API changes on existing features (you may need to change your code)
+* RESTCONF PUT/POST erroneously returned 200 OK. Instead restconf now returns:
+ * `201 Created` for created resources
+ * `204 No Content` for replaced resources.
+* RESTCONF identities has been changed to use module names instead of prefixes.
+ * Eg, `curl -X POST -d '{"type":"ex:eth"}` --> `curl -X POST -d '{"type":"ietf-interfaces:eth"`}
+* JSON changes
+ * Non-pretty-print output removed all extra spaces.
+ * Example: `{"nacm-example:x": 42}` --> {"nacm-example:x":42}`
+ * Empty JSON values changed from `null` to:
+ * Empty yang container encoded as `{}`
+ * Empty leaf/leaf-list of type empty encoded as `[null]`
+ * Other empty values remain as `null`
+
+### Minor changes
+* Removed unnecessary configure dependencies
+ * libnsl, libcrypt, if_vlan,...
+* pseudo-plugin added, to enable callbacks also for main programs. Useful for extensions
+
+### Corrected Bugs
+* Fixed RESTCONF api-path leaf-list selection was not made properly
+ * Requesting eg `mod:x/y=42` returned the whole list: `{"y":[41,42,43]}` whereas it should only return one element: `{"y":42}`
+* See [RESTCONF: HTTP return codes are not according to RFC 8040](https://github.com/clicon/clixon/issues/56)
+* Yang Unique statements with multiple schema identifiers did not work on some platforms due to memory error.
+
+## 4.0.0 (13 July 2019)
### Summary
diff --git a/README.md b/README.md
index 47b72648..fac2ee1e 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,6 @@ However, the following YANG syntax modules are not implemented (reference to RFC
- require-instance
- instance-identifier type
- status (7.21.2)
-- extension (7.19) supported syntactically, but no hooks/plugins for extenstions
- YIN (13)
- Yang extended Xpath functions: re-match(), deref)(), derived-from(), derived-from-or-self(), enum-value(), bit-is-set() (10.2-10.6)
- Default values on leaf-lists are not supported (7.7.2)
@@ -204,7 +203,6 @@ You can create namespace in three ways:
* `xml_nsctx_node()` by copying an XML namespace context from an existing XML node.
* `xml_nsctx_yang()` by computing an XML namespace context a yang module import statements.
-
## Netconf
Clixon implements the following NETCONF proposals or standards:
@@ -237,14 +235,16 @@ Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available t
run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
-The following features are supported:
+The following features of RFC8040 are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE
-- stream notifications (RFC8040 sec 6)
-- query parameters start-time and stop-time(RFC8040 section 4.9)
+- stream notifications (Sec 6)
+- query parameters: "insert", "point", "start-time" and "stop-time".
+- Monitoring (Sec 9)
The following features are not implemented:
+- ETag/Last-Modified
- PATCH
-- query parameters other than start/stop-time.
+- Query parameters: "content", "depth", "fields", "filter", "with-defaults"
See [more detailed instructions](apps/restconf/README.md).
@@ -308,17 +308,16 @@ The figure shows the SDK runtime of Clixon.
## Standard Compliance
-This is work-in-progress on which standards Clixon supports:
-- [RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)
+Standards Clixon partially supports:
+- [RFC5277](http://www.rfc-base.org/txt/rfc-5277.txt) NETCONF Event Notifications
+- [RFC6020](https://www.rfc-editor.org/rfc/rfc6020.txt) YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)
+- [RFC6241](http://www.rfc-base.org/txt/rfc-6241.txt) NETCONF Configuration Protocol
+- [RFC6242](http://www.rfc-base.org/txt/rfc-6242.txt) Using the NETCONF Configuration Protocol over Secure Shell (SSH)
- [RFC7895](http://www.rfc-base.org/txt/rfc-7895.txt) YANG Module Library
* [RFC7950](http://www.rfc-base.org/txt/rfc-7950.txt) The YANG 1.1 Data Modeling Language
* [RFC7951](http://www.rfc-base.org/txt/rfc-7951.txt) JSON Encoding of Data Modeled with YANG
-- [RFC 6241: NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-6241.txt)
-- [RFC 6242: Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-6242.txt)
-- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
-- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
-- [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
-- [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
+- [RFC8040](https://tools.ietf.org/html/rfc8040) RESTCONF Protocol
+- [RFC8341](http://www.rfc-base.org/txt/rfc-8341.txt) Network Configuration Access Control Model
- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126)
- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208)
- [XPATH 1.0](https://www.w3.org/TR/xpath-10)
diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in
index 45677854..0a3c0ea3 100644
--- a/apps/backend/Makefile.in
+++ b/apps/backend/Makefile.in
@@ -61,7 +61,7 @@ CLIXON_LIB = libclixon$(SH_SUFFIX).$(CLIXON_MAJOR).$(CLIXON_MINOR)
# even though it may exist in $(libdir). But the new version may not have been installed yet.
LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
-LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -lpthread
+LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB)
CPPFLAGS = @CPPFLAGS@ -fPIC
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 8280ebbe..07329adc 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -149,6 +149,37 @@ backend_client_rm(clicon_handle h,
return backend_client_delete(h, ce); /* actually purge it */
}
+/*!
+ * Maybe should be in the restconf client instead of backend?
+ * @param[in] h Clicon handle
+ * @param[in] yspec Yang spec
+ * @param[in] xpath Xpath selection, not used but may be to filter early
+ * @param[out] xrs XML restconf-state node
+ * @see netconf_create_hello
+ * @see rfc8040 Sections 9.1
+ */
+static int
+client_get_capabilities(clicon_handle h,
+ yang_stmt *yspec,
+ char *xpath,
+ cxobj **xret)
+{
+ int retval = -1;
+ cxobj *xrstate = NULL; /* xml restconf-state node */
+ cxobj *xcap = NULL; /* xml capabilities node */
+
+ if ((xrstate = xpath_first(*xret, "restconf-state")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "restconf-state not found in config node");
+ goto done;
+ }
+ if ((xcap = xml_new("capabilities", xrstate, yspec)) == NULL)
+ goto done;
+ if (xml_parse_va(&xcap, yspec, "urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit") < 0)
+ goto done;
+ retval = 0;
+ done:
+ return retval;
+}
/*! Get streams state according to RFC 8040 or RFC5277 common function
* @param[in] h Clicon handle
@@ -165,23 +196,18 @@ static int
client_get_streams(clicon_handle h,
yang_stmt *yspec,
char *xpath,
- char *module,
+ yang_stmt *ymod,
char *top,
cxobj **xret)
{
int retval = -1;
- yang_stmt *ystream = NULL; /* yang stream module */
yang_stmt *yns = NULL; /* yang namespace */
cxobj *x = NULL;
cbuf *cb = NULL;
int ret;
- if ((ystream = yang_find(yspec, Y_MODULE, module)) == NULL){
- clicon_err(OE_YANG, 0, "%s yang module not found", module);
- goto done;
- }
- if ((yns = yang_find(ystream, Y_NAMESPACE, NULL)) == NULL){
- clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
+ if ((yns = yang_find(ymod, Y_NAMESPACE, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "%s yang namespace not found", yang_argument_get(ymod));
goto done;
}
if ((cb = cbuf_new()) == NULL){
@@ -189,6 +215,9 @@ client_get_streams(clicon_handle h,
goto done;
}
cprintf(cb,"<%s xmlns=\"%s\">", top, yang_argument_get(yns));
+ /* Second argument is a hack to have the same function for the
+ * RFC5277 and 8040 stream cases
+ */
if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
goto done;
cprintf(cb,"%s>", top);
@@ -234,23 +263,47 @@ client_statedata(clicon_handle h,
size_t xlen;
int i;
yang_stmt *yspec;
+ yang_stmt *ymod;
int ret;
+ char *namespace;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")){
- if ((ret = client_get_streams(h, yspec, xpath, "clixon-rfc5277", "netconf", xret)) < 0)
+ if ((ymod = yang_find_module_by_name(yspec, "clixon-rfc5277")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "yang module clixon-rfc5277 not found");
+ goto done;
+ }
+ if ((namespace = yang_find_mynamespace(ymod)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "clixon-rfc5277 namespace not found");
+ goto done;
+ }
+ if (xml_parse_va(xret, yspec, "", namespace) < 0)
+ goto done;
+ if ((ret = client_get_streams(h, yspec, xpath, ymod, "netconf", xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040")){
- if ((ret = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) < 0)
+ if ((ymod = yang_find_module_by_name(yspec, "ietf-restconf-monitoring")) == NULL){
+ clicon_err(OE_YANG, ENOENT, "yang module ietf-restconf-monitoring not found");
+ goto done;
+ }
+ if ((namespace = yang_find_mynamespace(ymod)) == NULL){
+ clicon_err(OE_YANG, ENOENT, "ietf-restconf-monitoring namespace not found");
+ goto done;
+ }
+ if (xml_parse_va(xret, yspec, "", namespace) < 0)
+ goto done;
+ if ((ret = client_get_streams(h, yspec, xpath, ymod, "restconf-state", xret)) < 0)
goto done;
if (ret == 0)
goto fail;
+ if ((ret = client_get_capabilities(h, yspec, xpath, xret)) < 0)
+ goto done;
}
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895")){
if ((ret = yang_modules_state_get(h, yspec, xpath, nsc, 0, xret)) < 0)
@@ -1324,46 +1377,46 @@ backend_rpc_init(clicon_handle h)
/* In backend_client.? RFC 6241 */
if (rpc_callback_register(h, from_client_get_config, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "get-config") < 0)
+ NETCONF_BASE_NAMESPACE, "get-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_edit_config, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "edit-config") < 0)
+ NETCONF_BASE_NAMESPACE, "edit-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_copy_config, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "copy-config") < 0)
+ NETCONF_BASE_NAMESPACE, "copy-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_delete_config, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "delete-config") < 0)
+ NETCONF_BASE_NAMESPACE, "delete-config") < 0)
goto done;
if (rpc_callback_register(h, from_client_lock, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "lock") < 0)
+ NETCONF_BASE_NAMESPACE, "lock") < 0)
goto done;
if (rpc_callback_register(h, from_client_unlock, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "unlock") < 0)
+ NETCONF_BASE_NAMESPACE, "unlock") < 0)
goto done;
if (rpc_callback_register(h, from_client_get, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "get") < 0)
+ NETCONF_BASE_NAMESPACE, "get") < 0)
goto done;
if (rpc_callback_register(h, from_client_close_session, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "close-session") < 0)
+ NETCONF_BASE_NAMESPACE, "close-session") < 0)
goto done;
if (rpc_callback_register(h, from_client_kill_session, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "kill-session") < 0)
+ NETCONF_BASE_NAMESPACE, "kill-session") < 0)
goto done;
/* In backend_commit.? */
if (rpc_callback_register(h, from_client_commit, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "commit") < 0)
+ NETCONF_BASE_NAMESPACE, "commit") < 0)
goto done;
if (rpc_callback_register(h, from_client_discard_changes, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "discard-changes") < 0)
+ NETCONF_BASE_NAMESPACE, "discard-changes") < 0)
goto done;
/* if-feature confirmed-commit */
if (rpc_callback_register(h, from_client_cancel_commit, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "cancel-commit") < 0)
+ NETCONF_BASE_NAMESPACE, "cancel-commit") < 0)
goto done;
/* if-feature validate */
if (rpc_callback_register(h, from_client_validate, NULL,
- "urn:ietf:params:xml:ns:netconf:base:1.0", "validate") < 0)
+ NETCONF_BASE_NAMESPACE, "validate") < 0)
goto done;
/* In backend_client.? RPC from RFC 5277 */
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 5e9a5354..26fb145b 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -332,6 +332,7 @@ main(int argc,
cbuf *cbret = NULL; /* startup cbuf if invalid */
enum startup_status status = STARTUP_ERR; /* Startup status */
int ret;
+ char *dir;
/* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@@ -573,6 +574,13 @@ main(int argc,
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
+
+ /* Load backend plugins before yangs are loaded (eg extension callbacks) */
+ if ((dir = clicon_backend_dir(h)) != NULL &&
+ clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir,
+ clicon_option_str(h, "CLICON_BACKEND_REGEXP")) < 0)
+ goto done;
+
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL)
@@ -596,6 +604,9 @@ main(int argc,
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
+ /* Load yang restconf module */
+ if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
+ goto done;
/* Load yang Restconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
@@ -646,8 +657,6 @@ main(int argc,
goto done;
case SM_NONE: /* Fall through *
* Load plugins and call plugin_init() */
- if (backend_plugin_initiate(h) != 0)
- goto done;
status = STARTUP_OK;
break;
case SM_RUNNING: /* Use running as startup */
@@ -735,7 +744,6 @@ main(int argc,
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
-
/* Initialize server socket and save it to handle */
if ((ss = backend_server_socket(h)) < 0)
goto done;
diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c
index 711a8de0..a0dc92d3 100644
--- a/apps/backend/backend_plugin.c
+++ b/apps/backend/backend_plugin.c
@@ -63,23 +63,6 @@
#include "backend_plugin.h"
#include "backend_commit.h"
-/*! Load a plugin group.
- * @param[in] h Clicon handle
- * @retval 0 OK
- * @retval -1 Error
- */
-int
-backend_plugin_initiate(clicon_handle h)
-{
- char *dir;
-
- /* 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"));
-}
-
/*! Request plugins to reset system state
* The system 'state' should be the same as the contents of running_db
* @param[in] h Clicon handle
diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h
index 1071fc4e..270272c7 100644
--- a/apps/backend/backend_plugin.h
+++ b/apps/backend/backend_plugin.h
@@ -67,8 +67,6 @@ typedef struct {
/*
* Prototypes
*/
-int backend_plugin_initiate(clicon_handle h);
-
int clixon_plugin_reset(clicon_handle h, char *db);
int clixon_plugin_statedata(clicon_handle h, yang_stmt *yspec, cvec *nsc,
diff --git a/apps/backend/backend_startup.c b/apps/backend/backend_startup.c
index 7de92e44..35204067 100644
--- a/apps/backend/backend_startup.c
+++ b/apps/backend/backend_startup.c
@@ -138,9 +138,6 @@ startup_mode_startup(clicon_handle h,
clicon_err(OE_FATAL, 0, "Invalid startup db: %s", db);
goto done;
}
- /* Load plugins and call plugin_init() */
- if (backend_plugin_initiate(h) != 0)
- goto done;
/* If startup does not exist, create it empty */
if (xmldb_exists(h, db) != 1){ /* diff */
if (xmldb_create(h, db) < 0) /* diff */
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index 2bbce242..f0840491 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -47,9 +47,6 @@
#include
#include
-#ifdef HAVE_CRYPT_H
-#include
-#endif
#include
#include
#include
@@ -239,19 +236,15 @@ cli_dbxml(clicon_handle h,
enum operation_type op)
{
int retval = -1;
- // char *str = NULL;
- char *api_path_fmt; /* xml key format */
+ char *api_path_fmt; /* xml key format */
char *api_path = NULL; /* xml key */
- // cg_var *cval;
- // int len;
cg_var *arg;
cbuf *cb = NULL;
yang_stmt *yspec;
- cxobj *xbot = NULL; /* xpath, NULL if datastore */
- yang_stmt *y = NULL; /* yang spec of xpath */
- cxobj *xtop = NULL; /* xpath root */
- cxobj *xa; /* attribute */
- // cxobj *xb; /* body */
+ cxobj *xbot = NULL; /* xpath, NULL if datastore */
+ yang_stmt *y = NULL; /* yang spec of xpath */
+ cxobj *xtop = NULL; /* xpath root */
+ cxobj *xa; /* attribute */
if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string");
@@ -274,6 +267,7 @@ cli_dbxml(clicon_handle h,
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
+ xml_prefix_set(xa, NETCONF_BASE_PREFIX);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){
diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c
index 03535e27..5e3381be 100644
--- a/apps/cli/cli_generate.c
+++ b/apps/cli/cli_generate.c
@@ -52,7 +52,6 @@
#include
#include
#include
-#include /* For pow() kludge in cvtype_max2str_dup2 */
/* cligen */
#include
diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c
index 3ca2c904..4651c5fc 100644
--- a/apps/cli/cli_main.c
+++ b/apps/cli/cli_main.c
@@ -284,6 +284,7 @@ main(int argc, char **argv)
struct passwd *pw;
char *str;
int tabmode;
+ char *dir;
/* Defaults */
once = 0;
@@ -466,6 +467,11 @@ main(int argc, char **argv)
*/
cv_exclude_keys(clicon_cli_varonly(h));
+ /* Load cli plugins before yangs are loaded (eg extension callbacks) */
+ if ((dir = clicon_cli_dir(h)) != NULL &&
+ clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
+ goto done;
+
/* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
index 2b2f5791..31331f21 100644
--- a/apps/cli/cli_plugin.c
+++ b/apps/cli/cli_plugin.c
@@ -319,7 +319,6 @@ int
cli_syntax_load(clicon_handle h)
{
int retval = -1;
- char *plugin_dir = NULL;
char *clispec_dir = NULL;
char *clispec_file = NULL;
int ndp;
@@ -336,7 +335,6 @@ cli_syntax_load(clicon_handle h)
return 0;
/* Format plugin directory path */
- plugin_dir = clicon_cli_dir(h);
clispec_dir = clicon_clispec_dir(h);
clispec_file = clicon_option_str(h, "CLICON_CLISPEC_FILE");
@@ -349,10 +347,6 @@ cli_syntax_load(clicon_handle h)
cli_syntax_set(h, stx);
- /* 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 done;
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index 6cf77547..635bbf8b 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -48,9 +48,6 @@
#include
#include
-#ifdef HAVE_CRYPT_H
-#include
-#endif
#include
#include
#include
diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c
index 9c4b07ba..c7ab25ac 100644
--- a/apps/netconf/netconf_hello.c
+++ b/apps/netconf/netconf_hello.c
@@ -161,7 +161,7 @@ netconf_create_hello(clicon_handle h,
if ((ietf_yang_library_revision = yang_modules_revision(h)) == NULL)
goto done;
add_preamble(cb);
- cprintf(cb, "");
+ cprintf(cb, "", NETCONF_BASE_NAMESPACE);
cprintf(cb, "");
cprintf(cb, "urn:ietf:params:netconf:base:1.0");
if (xml_chardata_encode(&encstr, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c
index a2c90486..32e81496 100644
--- a/apps/netconf/netconf_main.c
+++ b/apps/netconf/netconf_main.c
@@ -488,6 +488,12 @@ main(int argc,
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
+
+ /* Load netconf plugins before yangs are loaded (eg extension callbacks) */
+ if ((dir = clicon_netconf_dir(h)) != NULL &&
+ clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
+ goto done;
+
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL){
@@ -514,15 +520,9 @@ main(int argc,
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
- /* Initialize plugins group */
- 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 */
if (clixon_plugin_start(h) < 0)
goto done;
-
if (!quiet)
send_hello(h, 1);
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in
index 9185c60c..b2936775 100644
--- a/apps/restconf/Makefile.in
+++ b/apps/restconf/Makefile.in
@@ -76,6 +76,9 @@ APPL = clixon_restconf
# Not accessible from plugin
APPSRC = restconf_main.c
APPSRC += restconf_methods.c
+APPSRC += restconf_methods_post.c
+APPSRC += restconf_methods_get.c
+APPSRC += restconf_methods_patch.c
APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o)
diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h
index 06c6ed86..38385d40 100644
--- a/apps/restconf/clixon_restconf.h
+++ b/apps/restconf/clixon_restconf.h
@@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
- Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
@@ -51,7 +51,7 @@ int notfound(FCGX_Request *r);
int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
-int test(FCGX_Request *r, int dbg);
+int restconf_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,
diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c
index e7613f87..f29f1b25 100644
--- a/apps/restconf/restconf_lib.c
+++ b/apps/restconf/restconf_lib.c
@@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
- Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
@@ -31,6 +31,7 @@
***** END LICENSE BLOCK *****
+ * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
#include
@@ -302,12 +303,13 @@ printparam(FCGX_Request *r,
return 0;
}
-/*!
+/*! Print all FCGI headers
* @param[in] r Fastcgi request handle
+ * @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
-test(FCGX_Request *r,
- int dbg)
+restconf_test(FCGX_Request *r,
+ int dbg)
{
printparam(r, "QUERY_STRING", dbg);
printparam(r, "REQUEST_METHOD", dbg);
@@ -328,6 +330,7 @@ test(FCGX_Request *r,
printparam(r, "SERVER_NAME", dbg);
printparam(r, "HTTP_COOKIE", dbg);
printparam(r, "HTTPS", dbg);
+ printparam(r, "HTTP_HOST", dbg);
printparam(r, "HTTP_ACCEPT", dbg);
printparam(r, "HTTP_CONTENT_TYPE", dbg);
printparam(r, "HTTP_AUTHORIZATION", dbg);
@@ -464,7 +467,7 @@ api_return_err(clicon_handle h,
}
else{
FCGX_FPrintF(r->out, "{");
- FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : ");
+ FCGX_FPrintF(r->out, "\"ietf-restconf:errors\":");
FCGX_FPrintF(r->out, "%s", cbuf_get(cb));
FCGX_FPrintF(r->out, "}\r\n");
}
@@ -478,6 +481,50 @@ api_return_err(clicon_handle h,
return retval;
}
+/*! Print location header from FCGI environment
+ * @param[in] r Fastcgi request handle
+ * @param[in] xobj If set (eg POST) add to api-path
+ * $https “on” if connection operates in SSL mode, or an empty string otherwise
+ * @note ports are ignored
+ */
+int
+http_location(FCGX_Request *r,
+ cxobj *xobj)
+{
+ int retval = -1;
+ char *https;
+ char *host;
+ char *request_uri;
+ cbuf *cb = NULL;
+
+ https = FCGX_GetParam("HTTPS", r->envp);
+ host = FCGX_GetParam("HTTP_HOST", r->envp);
+ request_uri = FCGX_GetParam("REQUEST_URI", r->envp);
+ if (xobj != NULL){
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, 0, "cbuf_new");
+ goto done;
+ }
+ if (xml2api_path_1(xobj, cb) < 0)
+ goto done;
+ FCGX_FPrintF(r->out, "Location: http%s://%s%s%s\r\n",
+ https?"s":"",
+ host,
+ request_uri,
+ cbuf_get(cb));
+ }
+ else
+ FCGX_FPrintF(r->out, "Location: http%s://%s%s\r\n",
+ https?"s":"",
+ host,
+ request_uri);
+ retval = 0;
+ done:
+ if (cb)
+ cbuf_free(cb);
+ return retval;
+}
+
/*! Clean and close all state of restconf process (but dont exit).
* Cannot use h after this
* @param[in] h Clixon handle
@@ -506,3 +553,96 @@ restconf_terminate(clicon_handle h)
return 0;
}
+/*! If restconf insert/point attributes are present, translate to netconf
+ * @param[in] xdata URI->XML to translate
+ * @param[in] qvec Query parameters (eg where insert/point should be)
+ * @retval 0 OK
+ * @retval -1 Error
+ */
+int
+restconf_insert_attributes(cxobj *xdata,
+ cvec *qvec)
+{
+ int retval = -1;
+ cxobj *xa;
+ char *instr;
+ char *pstr;
+ yang_stmt *y;
+ char *attrname;
+ int ret;
+
+ y = xml_spec(xdata);
+ if ((instr = cvec_find_str(qvec, "insert")) != NULL){
+ /* First add xmlns:yang attribute */
+ if ((xa = xml_new("yang", xdata, NULL)) == NULL)
+ goto done;
+ if (xml_prefix_set(xa, "xmlns") < 0)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if (xml_value_set(xa, YANG_XML_NAMESPACE) < 0)
+ goto done;
+ /* Then add insert attribute */
+ if ((xa = xml_new("insert", xdata, NULL)) == NULL)
+ goto done;
+ if (xml_prefix_set(xa, "yang") < 0)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if (xml_value_set(xa, instr) < 0)
+ goto done;
+ }
+ if ((pstr = cvec_find_str(qvec, "point")) != NULL){
+ char *xpath = NULL;
+ char *namespace = NULL;
+ cbuf *cb = NULL;
+ if (y == NULL){
+ clicon_err(OE_YANG, 0, "Cannot yang resolve %s", xml_name(xdata));
+ goto done;
+ }
+ if (yang_keyword_get(y) == Y_LIST)
+ attrname="key";
+ else
+ attrname="value";
+ /* Then add value/key attribute */
+ if ((xa = xml_new(attrname, xdata, NULL)) == NULL)
+ goto done;
+ if (xml_prefix_set(xa, "yang") < 0)
+ goto done;
+ xml_type_set(xa, CX_ATTR);
+ if ((ret = api_path2xpath(pstr, ys_spec(y), &xpath, &namespace)) < 0)
+ goto done;
+ if ((cb = cbuf_new()) == NULL){
+ clicon_err(OE_UNIX, errno, "cbuf_new");
+ goto done;
+ }
+ cprintf(cb, "/%s", xpath); /* XXX: also prefix/namespace? */
+ if (xml_value_set(xa, cbuf_get(cb)) < 0)
+ goto done;
+ if (xpath)
+ free(xpath);
+ if (cb)
+ cbuf_free(cb);
+ }
+ retval = 0;
+ done:
+ return retval;
+}
+
+/*! Extract uri-encoded uri-path from fastcgi parameters
+ * Use REQUEST_URI parameter and strip ?args
+ * REQUEST_URI have args and is encoded
+ * eg /interface=eth%2f0%2f0?insert=first
+ * DOCUMENT_URI dont have args and is not encoded
+ * eg /interface=eth/0/0
+ * causes problems with eg /interface=eth%2f0%2f0
+ */
+char *
+restconf_uripath(FCGX_Request *r)
+{
+ char *path;
+ char *q;
+
+ path = FCGX_GetParam("REQUEST_URI", r->envp);
+ if ((q = index(path, '?')) != NULL)
+ *q = '\0';
+ return path;
+}
diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h
index 1b90ef30..6fe968e1 100644
--- a/apps/restconf/restconf_lib.h
+++ b/apps/restconf/restconf_lib.h
@@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
- Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
@@ -56,11 +56,14 @@ int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
-int test(FCGX_Request *r, int dbg);
+int restconf_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 code);
+int http_location(FCGX_Request *r, cxobj *xobj);
int restconf_terminate(clicon_handle h);
+int restconf_insert_attributes(cxobj *xdata, cvec *qvec);
+char *restconf_uripath(FCGX_Request *r);
#endif /* _RESTCONF_LIB_H_ */
diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c
index cc6dabef..6f495f89 100644
--- a/apps/restconf/restconf_main.c
+++ b/apps/restconf/restconf_main.c
@@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
- Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
@@ -78,6 +78,9 @@
/* restconf */
#include "restconf_lib.h"
#include "restconf_methods.h"
+#include "restconf_methods_get.h"
+#include "restconf_methods_post.h"
+#include "restconf_methods_patch.h"
#include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */
@@ -190,6 +193,7 @@ api_well_known(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
+ FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_SetExitStatus(200, r->out); /* OK */
@@ -210,24 +214,32 @@ static int
api_root(clicon_handle h,
FCGX_Request *r)
{
- int retval = -1;
- char *media_accept;
- int use_xml = 0; /* By default use JSON */
- cxobj *xt = NULL;
- cbuf *cb = NULL;
- int pretty;
+ int retval = -1;
+ char *media_accept;
+ int use_xml = 0; /* By default use JSON */
+ cxobj *xt = NULL;
+ cbuf *cb = NULL;
+ int pretty;
+ yang_stmt *yspec;
clicon_debug(1, "%s", __FUNCTION__);
+ if ((yspec = clicon_dbspec_yang(h)) == NULL){
+ clicon_err(OE_FATAL, 0, "No DB_SPEC");
+ goto done;
+ }
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++;
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, "Cache-Control: no-cache\r\n");
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)
+ if (xml_parse_string("2016-06-21", NULL, &xt) < 0)
+ goto done;
+ if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@@ -274,6 +286,7 @@ api_yang_library_version(clicon_handle h,
if (strcmp(media_accept, "application/yang-data+xml")==0)
use_xml++;
FCGX_SetExitStatus(200, r->out); /* OK */
+ FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
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_va(&xt, NULL, "%s", ietf_yang_library_revision) < 0)
@@ -332,7 +345,7 @@ api_restconf(clicon_handle h,
cxobj *xerr;
clicon_debug(1, "%s", __FUNCTION__);
- path = FCGX_GetParam("REQUEST_URI", r->envp);
+ path = restconf_uripath(r);
query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* get xml/json in put and output */
@@ -358,7 +371,7 @@ api_restconf(clicon_handle h,
retval = notfound(r);
goto done;
}
- test(r, 1);
+ restconf_test(r, 1);
if (pn == 2){
retval = api_root(h, r);
@@ -419,7 +432,7 @@ api_restconf(clicon_handle h,
goto done;
}
else if (strcmp(method, "test") == 0)
- test(r, 0);
+ restconf_test(r, 0);
else
notfound(r);
ok:
@@ -467,6 +480,44 @@ restconf_sig_term(int arg)
exit(-1);
}
+/*! Callback for yang extensions ietf-restconf:yang-data
+ * @see ietf-restconf.yang
+ * @param[in] h Clixon handle
+ * @param[in] yext Yang node of extension
+ * @param[in] ys Yang node of (unknown) statement belonging to extension
+ * @retval 0 OK, all callbacks executed OK
+ * @retval -1 Error in one callback
+ */
+static int
+restconf_main_extension_cb(clicon_handle h,
+ yang_stmt *yext,
+ yang_stmt *ys)
+{
+ int retval = -1;
+ char *extname;
+ char *modname;
+ yang_stmt *ymod;
+ yang_stmt *yc;
+ yang_stmt *yn = NULL;
+
+ ymod = ys_module(yext);
+ modname = yang_argument_get(ymod);
+ extname = yang_argument_get(yext);
+ if (strcmp(modname, "ietf-restconf") != 0 || strcmp(extname, "yang-data") != 0)
+ goto ok;
+ clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
+ if ((yc = yang_find(ys, 0, NULL)) == NULL)
+ goto ok;
+ if ((yn = ys_dup(yc)) == NULL)
+ goto done;
+ if (yn_insert(yang_parent_get(ys), yn) < 0)
+ goto done;
+ ok:
+ retval = 0;
+ done:
+ return retval;
+}
+
static void
restconf_sig_child(int arg)
{
@@ -510,22 +561,23 @@ int
main(int argc,
char **argv)
{
- int retval = -1;
- int sock;
- char *argv0 = argv[0];
- FCGX_Request request;
- FCGX_Request *r = &request;
- int c;
- char *sockpath;
- char *path;
- clicon_handle h;
- char *dir;
- int logdst = CLICON_LOG_SYSLOG;
- yang_stmt *yspec = NULL;
- yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
- char *stream_path;
- int finish;
- char *str;
+ int retval = -1;
+ int sock;
+ char *argv0 = argv[0];
+ FCGX_Request request;
+ FCGX_Request *r = &request;
+ int c;
+ char *sockpath;
+ char *path;
+ clicon_handle h;
+ char *dir;
+ int logdst = CLICON_LOG_SYSLOG;
+ yang_stmt *yspec = NULL;
+ yang_stmt *yspecfg = NULL; /* For config XXX clixon bug */
+ char *stream_path;
+ int finish;
+ char *str;
+ clixon_plugin *cp = NULL;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@@ -635,15 +687,21 @@ main(int argc,
/* Access the remaining argv/argc options (after --) w clicon-argv_get() */
clicon_argv_set(h, argv0, argc, argv);
- /* Initialize plugins group */
- if ((dir = clicon_restconf_dir(h)) != NULL)
- if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
- return -1;
-
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
- clicon_dbspec_yang_set(h, yspec);
+ clicon_dbspec_yang_set(h, yspec);
+
+ /* Load restconf plugins before yangs are loaded (eg extension callbacks) */
+ if ((dir = clicon_restconf_dir(h)) != NULL)
+ if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
+ return -1;
+ /* Create a pseudo-plugin to create extension callback to set the ietf-routing
+ * yang-data extension for api-root top-level restconf function.
+ */
+ if (clixon_pseudo_plugin(h, "pseudo restconf", &cp) < 0)
+ goto done;
+ cp->cp_api.ca_extension = restconf_main_extension_cb;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
@@ -669,6 +727,10 @@ main(int argc,
if (yang_modules_init(h) < 0)
goto done;
+ /* Load yang restconf module */
+ if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
+ goto done;
+
/* Add netconf yang spec, used as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index cc7f9f49..e795d58a 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -2,7 +2,7 @@
*
***** BEGIN LICENSE BLOCK *****
- Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
+ Copyright (C) 2009-2019 Olof Hagsand
This file is part of CLIXON.
@@ -146,549 +146,6 @@ api_data_options(clicon_handle h,
return 0;
}
-
-/*! Generic GET (both HEAD and GET)
- * According to restconf
- * @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 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
- * Accept: application/yang.data+json,application/yang.data+xml
- * Response contains one of:
- * Content-Type: application/yang-data+xml
- * Content-Type: application/yang-data+json
- * NOTE: If a retrieval request for a data resource representing a YANG leaf-
- * list or list object identifies more than one instance, and XML
- * encoding is used in the response, then an error response containing a
- * "400 Bad Request" status-line MUST be returned by the server.
- * Netconf: ,
- */
-static int
-api_data_get2(clicon_handle h,
- FCGX_Request *r,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- int pretty,
- int use_xml,
- int head)
-{
- int retval = -1;
- cbuf *cbpath = NULL;
- char *xpath = NULL;
- cbuf *cbx = NULL;
- yang_stmt *yspec;
- cxobj *xret = NULL;
- cxobj *xerr = NULL; /* malloced */
- cxobj *xe = NULL; /* not malloced */
- cxobj **xvec = NULL;
- size_t xlen;
- int i;
- cxobj *x;
- int ret;
- char *namespace = NULL;
- cvec *nsc = NULL;
-
- clicon_debug(1, "%s", __FUNCTION__);
- yspec = clicon_dbspec_yang(h);
- if ((cbpath = cbuf_new()) == NULL)
- goto done;
- cprintf(cbpath, "/");
- /* We know "data" is element pi-1 */
- if ((ret = api_path2xpath_cvv(pcvec, pi, yspec, cbpath, &namespace)) < 0)
- goto done;
- if (ret == 0){
- if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
- goto done;
- clicon_err_reset();
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto ok;
- }
- xpath = cbuf_get(cbpath);
- clicon_debug(1, "%s path:%s", __FUNCTION__, xpath);
- /* Create a namespace context for ymod as the default namespace to use with
- * xpath expressions */
- if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
- goto done;
- if (clicon_rpc_get(h, xpath, namespace, &xret) < 0){
- if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto ok;
- }
- if (xml_apply(xret, CX_ELMNT, xml_spec_populate, yspec) < 0)
- goto done;
- /* We get return via netconf which is complete tree from root
- * We need to cut that tree to only the object.
- */
-#if 0 /* DEBUG */
- if (debug){
- cbuf *cb = cbuf_new();
- clicon_xml2cbuf(cb, xret, 0, 0);
- clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
- cbuf_free(cb);
- }
-#endif
- /* Check if error return */
- if ((xe = xpath_first(xret, "//rpc-error")) != NULL){
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto ok;
- }
- /* Normal return, no error */
- if ((cbx = cbuf_new()) == NULL)
- goto done;
- if (head){
- 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");
- goto ok;
- }
- if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
- if (use_xml){
- if (clicon_xml2cbuf(cbx, xret, 0, pretty) < 0) /* Dont print top object? */
- goto done;
- }
- else{
- if (xml2json_cbuf(cbx, xret, pretty) < 0)
- goto done;
- }
- }
- else{
- if (xpath_vec_nsc(xret, nsc, "%s", &xvec, &xlen, xpath) < 0){
- if (netconf_operation_failed_xml(&xerr, "application", clicon_err_reason) < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto ok;
- }
- /* Check if not exists */
- if (xlen == 0){
- /* 4.3: If a retrieval request for a data resource represents an
- instance that does not exist, then an error response containing
- a "404 Not Found" status-line MUST be returned by the server.
- The error-tag value "invalid-value" is used in this case. */
- if (netconf_invalid_value_xml(&xerr, "application", "Instance does not exist") < 0)
- goto done;
- /* override invalid-value default 400 with 404 */
- if (api_return_err(h, r, xerr, pretty, use_xml, 404) < 0)
- goto done;
- goto ok;
- }
- if (use_xml){
- for (i=0; i0
- * Out: {"example:x": {"0"}}
- */
- if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0)
- goto done;
- }
- }
- clicon_debug(1, "%s cbuf:%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 (nsc)
- xml_nsctx_free(nsc);
- if (cbx)
- cbuf_free(cbx);
- if (cbpath)
- cbuf_free(cbpath);
- if (xret)
- xml_free(xret);
- if (xerr)
- xml_free(xerr);
- if (xvec)
- free(xvec);
- return retval;
-}
-
-/*! REST HEAD method
- * @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 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
- *
- * 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,
- int pretty,
- int use_xml)
-{
- return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 1);
-}
-
-/*! REST GET method
- * According to restconf
- * @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 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
- * 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
- * Accept: application/yang.data+json,application/yang.data+xml
- * Response contains one of:
- * Content-Type: application/yang-data+xml
- * Content-Type: application/yang-data+json
- * NOTE: If a retrieval request for a data resource representing a YANG leaf-
- * list or list object identifies more than one instance, and XML
- * encoding is used in the response, then an error response containing a
- * "400 Bad Request" status-line MUST be returned by the server.
- * Netconf: ,
- */
-int
-api_data_get(clicon_handle h,
- FCGX_Request *r,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- int pretty,
- int use_xml)
-{
- return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 0);
-}
-
-/*! Generic REST POST method
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
- * @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
-
- * @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
-
- The message-body MUST contain exactly one instance of the
- expected data resource. The data model for the child tree is the
- subtree, as defined by YANG for the child resource.
-
- If the POST method succeeds, a "201 Created" status-line is returned
- and there is no response message-body. A "Location" header
- identifying the child resource that was created MUST be present in
- the response in this case.
-
- If the data resource already exists, then the POST request MUST fail
- and a "409 Conflict" status-line MUST be returned.
- * Netconf: (nc:operation="create") | invoke an RPC operation * @example
- */
-int
-api_data_post(clicon_handle h,
- FCGX_Request *r,
- char *api_path,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- char *data,
- int pretty,
- int use_xml,
- int parse_xml)
-{
- int retval = -1;
- enum operation_type op = OP_CREATE;
- cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
- cxobj *xdata; /* -d data (without top symbol)*/
- int i;
- cbuf *cbx = NULL;
- cxobj *xtop = NULL; /* top of api-path */
- cxobj *xbot = NULL; /* bottom of api-path */
- yang_stmt *ybot = NULL; /* yang of xbot */
- yang_stmt *ymodapi = NULL; /* yang module of api-path (if any) */
- yang_stmt *ymoddata = NULL; /* yang module of data (-d) */
- yang_stmt *yspec;
- cxobj *xa;
- cxobj *xret = NULL;
- cxobj *xretcom = NULL; /* return from commit */
- cxobj *xretdis = NULL; /* return from discard-changes */
- cxobj *xerr = NULL; /* malloced must be freed */
- cxobj *xe; /* dont free */
- char *username;
- int ret;
-
- clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
- __FUNCTION__,
- api_path, data);
- if ((yspec = clicon_dbspec_yang(h)) == NULL){
- clicon_err(OE_FATAL, 0, "No DB_SPEC");
- goto done;
- }
- for (i=0; i", 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_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) < 0)
- goto done;
- goto ok;
- }
- /* Assume this is validation failed since commit includes validate */
- cbuf_reset(cbx);
- /* commit/discard should be done automaticaly by the system, therefore
- * recovery user is used here (edit-config but not commit may be permitted
- by NACM */
- cprintf(cbx, "", NACM_RECOVERY_USER);
- cprintf(cbx, "");
- if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
- goto done;
- if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
- cbuf_reset(cbx);
- cprintf(cbx, "", username?username:"");
- cprintf(cbx, "");
- if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
- goto done;
- /* log errors from discard, but ignore */
- if ((xpath_first(xretdis, "//rpc-error")) != NULL)
- clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0) /* Use original xe */
- goto done;
- goto ok;
- }
- if (xretcom){ /* Clear: can be reused again below */
- xml_free(xretcom);
- xretcom = NULL;
- }
- if (if_feature(yspec, "ietf-netconf", "startup")){
- /* RFC8040 Sec 1.4:
- * If the NETCONF server supports :startup, the RESTCONF server MUST
- * automatically update the non-volatile startup configuration
- * datastore, after the "running" datastore has been altered as a
- * consequence of a RESTCONF edit operation.
- */
- cbuf_reset(cbx);
- cprintf(cbx, "", NACM_RECOVERY_USER);
- cprintf(cbx, "");
- if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
- goto done;
- /* If copy-config failed, log and ignore (already committed) */
- if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
-
- clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
- }
- }
-
- FCGX_SetExitStatus(201, r->out); /* Created */
- FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
- FCGX_FPrintF(r->out, "\r\n");
- ok:
- 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 (xretdis)
- xml_free(xretdis);
- if (xtop)
- xml_free(xtop);
- if (xdata0)
- xml_free(xdata0);
- if (cbx)
- cbuf_free(cbx);
- return retval;
-} /* api_data_post */
-
-
/*! Check matching keys
*
* Check that x1 and x2 are of type list/leaf-list and share the same key statements
@@ -771,16 +228,28 @@ match_list_keys(yang_stmt *y,
* @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
+ * @see RFC8040 Sec 4.5 PUT
+ * @see api_data_post
* @example
curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
- if the PUT request creates a new resource,
- a "201 Created" status-line is returned. If an existing resource is
- modified, a "204 No Content" status-line is returned.
+ A request message-body MUST be present, representing the new data resource, or the server
+ MUST return a "400 Bad Request" status-line.
+
+ ...if the PUT request creates a new resource, a "201 Created" status-line is returned.
+ If an existing resource is modified, a "204 No Content" status-line is returned.
* Netconf: (nc:operation="create/replace")
+ * Note RFC8040 says that if an object is created, 201 is returned, if replaced 204
+ * is returned. But the restconf client does not know if it is replaced or created,
+ * only the server knows that. Solutions:
+ * 1) extend the netconf so it returns if created/replaced. But that would lead
+ * to extension of netconf that may hit other places.
+ * 2) Send a get first and see if the resource exists, and then send replace/create.
+ * Will always produce an extra message and the GET may potetnially waste bw.
+ * 3) Try to create first, if that fails (with conflict) then try replace.
+ * --> Best solution and applied here
*/
int
api_data_put(clicon_handle h,
@@ -795,7 +264,7 @@ api_data_put(clicon_handle h,
int parse_xml)
{
int retval = -1;
- enum operation_type op = OP_REPLACE;
+ enum operation_type op;
int i;
cxobj *xdata0 = NULL; /* Original -d data struct (including top symbol) */
cxobj *xdata; /* -d data (without top symbol)*/
@@ -819,9 +288,10 @@ api_data_put(clicon_handle h,
int ret;
char *namespace0;
char *dname;
+ int nullspec = 0;
- clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
- __FUNCTION__, api_path0, data);
+ clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
+ clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@@ -868,6 +338,12 @@ api_data_put(clicon_handle h,
}
}
else{
+ /* Data here cannot cannot be Yang populated since it is loosely
+ * hanging without top symbols.
+ * And if it is not yang populated, it cant be translated properly
+ * from JSON to XML.
+ * Therefore, yang population is done later after addsub below
+ */
if ((ret = json_parse_str(data, yspec, &xdata0, &xerr)) < 0){
if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
goto done;
@@ -889,6 +365,7 @@ api_data_put(clicon_handle h,
goto ok;
}
}
+
/* The message-body MUST contain exactly one instance of the
* expected data resource.
*/
@@ -904,6 +381,15 @@ api_data_put(clicon_handle h,
goto ok;
}
xdata = xml_child_i(xdata0,0);
+#if 0
+ if (debug){
+ cbuf *ccc=cbuf_new();
+ if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0)
+ goto done;
+ clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc));
+ cbuf_free(ccc);
+ }
+#endif
/* If the api-path (above) defines a module, then xdata must have a prefix
* and it match the module defined in api-path
* This does not apply if api-path is / (no module)
@@ -924,13 +410,15 @@ api_data_put(clicon_handle h,
}
}
- /* Add operation (create/replace) as attribute */
+ /* Add operation create as attribute. If that fails with Conflict, then try
+ "replace" */
if ((xa = xml_new("operation", xdata, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
+ xml_prefix_set(xa, NETCONF_BASE_PREFIX);
+ op = OP_CREATE;
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
-
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data
*/
@@ -1017,6 +505,27 @@ api_data_put(clicon_handle h,
xml_purge(xbot);
if (xml_addsub(xparent, xdata) < 0)
goto done;
+ nullspec = (xml_spec(xdata) == NULL);
+ /* xbot is already populated, resolve yang for added xdata too */
+ if (xml_apply0(xdata, CX_ELMNT, xml_spec_populate, yspec) < 0)
+ goto done;
+ if (!parse_xml && nullspec){
+ /* json2xml decode could not be done above in json_parse,
+ * need to be done here instead
+ * UNLESS it is root resource, then json-parse has already done it
+ */
+ if ((ret = json2xml_decode(xdata, &xerr)) < 0)
+ goto done;
+ if (ret == 0){
+ if (api_return_err(h, r, xerr, pretty, use_xml, 0) < 0)
+ goto done;
+ goto ok;
+ }
+ }
+ /* If restconf insert/point attributes are present, translate to netconf */
+ if (restconf_insert_attributes(xdata, qvec) < 0)
+ goto done;
+
/* If we already have that default namespace, remove it in child */
if ((xa = xml_find_type(xdata, NULL, "xmlns", CX_ATTR)) != NULL){
if (xml2ns(xparent, NULL, &namespace0) < 0)
@@ -1025,6 +534,7 @@ api_data_put(clicon_handle h,
if (strcmp(namespace0, xml_value(xa))==0)
xml_purge(xa);
}
+
}
/* Create text buffer for transfer to backend */
if ((cbx = cbuf_new()) == NULL)
@@ -1032,7 +542,11 @@ api_data_put(clicon_handle h,
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
- cprintf(cbx, "", username?username:"");
+ again:
+ cprintf(cbx, "",
+ username?username:"",
+ NETCONF_BASE_PREFIX,
+ NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "");
cprintf(cbx, "none");
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
@@ -1042,9 +556,30 @@ api_data_put(clicon_handle h,
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) < 0)
+ /* If the error is not data-exists, then return error now
+ * OR we have run again with replace
+ */
+ if (xpath_first(xe, ".[error-tag=\"data-exists\"]") == NULL ||
+ op == OP_REPLACE){
+ if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
+ goto done;
+ goto ok;
+ }
+ /* If it is data-exists, then set operator to replace and try again */
+ if (xret){
+ xml_free(xret);
+ xret = NULL;
+ }
+ if ((xa = xml_find_type(xdata, NULL, "operation", CX_ATTR)) == NULL){
+ clicon_err(OE_XML, ENOENT, "operation attr not found (shouldnt happen)");
goto done;
- goto ok;
+ }
+ op = OP_REPLACE;
+ if (xml_value_set(xa, xml_operation2str(op)) < 0)
+ goto done;
+ cbuf_reset(cbx);
+ clicon_debug(1, "%s Failed with create, trying replace",__FUNCTION__);
+ goto again;
}
cbuf_reset(cbx);
/* commit/discard should be done automaticaly by the system, therefore
@@ -1089,7 +624,15 @@ api_data_put(clicon_handle h,
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
- FCGX_SetExitStatus(201, r->out); /* Created */
+ /* Check if it was created, or if we tried again and replaced it */
+ if (op == OP_CREATE){
+ FCGX_SetExitStatus(201, r->out); /* Created */
+ FCGX_FPrintF(r->out, "Status: 201 Created\r\n");
+ }
+ else{
+ FCGX_SetExitStatus(204, r->out); /* Replaced */
+ FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
+ }
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
ok:
@@ -1113,29 +656,6 @@ api_data_put(clicon_handle h,
return retval;
} /* api_data_put */
-/*! Generic REST PATCH method
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
- * @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
- * 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)
-{
- notimplemented(r);
- return 0;
-}
/*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle
@@ -1204,6 +724,7 @@ api_data_delete(clicon_handle h,
if ((xa = xml_new("operation", xbot, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
+ xml_prefix_set(xa, NETCONF_BASE_PREFIX);
if (xml_value_set(xa, xml_operation2str(op)) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
@@ -1211,7 +732,10 @@ api_data_delete(clicon_handle h,
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
- cprintf(cbx, "", username?username:"");
+ cprintf(cbx, "",
+ username?username:"",
+ NETCONF_BASE_PREFIX,
+ NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "");
cprintf(cbx, "none");
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
@@ -1226,7 +750,7 @@ api_data_delete(clicon_handle h,
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
- /* commit/discard should be done automaticaly by the system, therefore
+ /* commit/discard should be done automatically by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "", NACM_RECOVERY_USER);
@@ -1268,7 +792,8 @@ api_data_delete(clicon_handle h,
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
- FCGX_SetExitStatus(201, r->out);
+ FCGX_SetExitStatus(204, r->out);
+ FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
ok:
@@ -1288,642 +813,3 @@ api_data_delete(clicon_handle h,
return retval;
}
-/*! GET restconf/operations resource
- * @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 path starts
- * @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
- *
- * @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.
- * From ietf-restconf.yang:
- * In XML, the YANG module namespace identifies the module:
- *
- * In JSON, the YANG module name identifies the module:
- * { 'ietf-system:system-restart' : [null] }
- */
-int
-api_operations_get(clicon_handle h,
- FCGX_Request *r,
- char *path,
- cvec *pcvec,
- int pi,
- cvec *qvec,
- char *data,
- int pretty,
- int use_xml)
-{
- int retval = -1;
- yang_stmt *yspec;
- yang_stmt *ymod; /* yang module */
- yang_stmt *yc;
- char *namespace;
- cbuf *cbx = NULL;
- cxobj *xt = NULL;
- int i;
-
- clicon_debug(1, "%s", __FUNCTION__);
- yspec = clicon_dbspec_yang(h);
- if ((cbx = cbuf_new()) == NULL)
- goto done;
- if (use_xml)
- cprintf(cbx, "");
- else
- cprintf(cbx, "{\"operations\": {");
- ymod = NULL;
- i = 0;
- while ((ymod = yn_each(yspec, ymod)) != NULL) {
- namespace = yang_find_mynamespace(ymod);
- yc = NULL;
- while ((yc = yn_each(ymod, yc)) != NULL) {
- if (yang_keyword_get(yc) != Y_RPC)
- continue;
- if (use_xml)
- cprintf(cbx, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
- else{
- if (i++)
- cprintf(cbx, ",");
- cprintf(cbx, "\"%s:%s\": null", yang_argument_get(ymod), yang_argument_get(yc));
- }
- }
- }
- if (use_xml)
- cprintf(cbx, "");
- else
- cprintf(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;
-}
-
-/*! Handle input data to api_operations_post
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] data Stream input data
- * @param[in] yspec Yang top-level specification
- * @param[in] yrpc Yang rpc spec
- * @param[in] xrpc XML pointer to rpc method
- * @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
- * @retval 1 OK
- * @retval 0 Fail, Error message sent
- * @retval -1 Fatal error, clicon_err called
- *
- * RFC8040 3.6.1
- * If the "rpc" or "action" statement has an "input" section, then
- * instances of these input parameters are encoded in the module
- * namespace where the "rpc" or "action" statement is defined, in an XML
- * element or JSON object named "input", which is in the module
- * namespace where the "rpc" or "action" statement is defined.
- * (Any other input is assumed as error.)
- */
-static int
-api_operations_post_input(clicon_handle h,
- FCGX_Request *r,
- char *data,
- yang_stmt *yspec,
- yang_stmt *yrpc,
- cxobj *xrpc,
- int pretty,
- int use_xml,
- int parse_xml)
-{
- int retval = -1;
- cxobj *xdata = NULL;
- cxobj *xerr = NULL; /* malloced must be freed */
- cxobj *xe;
- cxobj *xinput;
- cxobj *x;
- cbuf *cbret = NULL;
- int ret;
-
- clicon_debug(1, "%s %s", __FUNCTION__, data);
- if ((cbret = cbuf_new()) == NULL){
- clicon_err(OE_UNIX, 0, "cbuf_new");
- goto done;
- }
- /* Parse input data as json or xml into xml */
- if (parse_xml){
- if (xml_parse_string(data, yspec, &xdata) < 0){
- if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
- }
- else { /* JSON */
- if ((ret = json_parse_str(data, yspec, &xdata, &xerr)) < 0){
- if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
- if (ret == 0){
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
- }
- xml_name_set(xdata, "data");
- /* Here xdata is:
- * ...
- */
-#if 1
- if (debug){
- cbuf *ccc=cbuf_new();
- if (clicon_xml2cbuf(ccc, xdata, 0, 0) < 0)
- goto done;
- clicon_debug(1, "%s DATA:%s", __FUNCTION__, cbuf_get(ccc));
- }
-#endif
- /* Validate that exactly only tag */
- if ((xinput = xml_child_i_type(xdata, 0, CX_ELMNT)) == NULL ||
- strcmp(xml_name(xinput),"input") != 0 ||
- xml_child_nr_type(xdata, CX_ELMNT) != 1){
-
- if (xml_child_nr_type(xdata, CX_ELMNT) == 0){
- if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have input statement") < 0)
- goto done;
- }
- else
- if (netconf_malformed_message_xml(&xerr, "restconf RPC has malformed input statement (multiple or not called input)") < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
- // clicon_debug(1, "%s input validation passed", __FUNCTION__);
- /* Add all input under path */
- x = NULL;
- while ((x = xml_child_i_type(xinput, 0, CX_ELMNT)) != NULL)
- if (xml_addsub(xrpc, x) < 0)
- goto done;
- /* Here xrpc is: 42
- */
- // ok:
- retval = 1;
- done:
- clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
- if (cbret)
- cbuf_free(cbret);
- if (xerr)
- xml_free(xerr);
- if (xdata)
- xml_free(xdata);
- return retval;
- fail:
- retval = 0;
- goto done;
-}
-
-/*! Handle output data to api_operations_post
- * @param[in] h CLIXON handle
- * @param[in] r Fastcgi request handle
- * @param[in] xret XML reply messages from backend/handler
- * @param[in] yspec Yang top-level specification
- * @param[in] youtput Yang rpc output specification
- * @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[out] xoutputp Restconf JSON/XML output
- * @retval 1 OK
- * @retval 0 Fail, Error message sent
- * @retval -1 Fatal error, clicon_err called
- * xret should like: 0
- */
-static int
-api_operations_post_output(clicon_handle h,
- FCGX_Request *r,
- cxobj *xret,
- yang_stmt *yspec,
- yang_stmt *youtput,
- char *namespace,
- int pretty,
- int use_xml,
- cxobj **xoutputp)
-
-{
- int retval = -1;
- cxobj *xoutput = NULL;
- cxobj *xerr = NULL; /* assumed malloced, will be freed */
- cxobj *xe; /* just pointer */
- cxobj *xa; /* xml attribute (xmlns) */
- cxobj *x;
- cxobj *xok;
- int isempty;
-
- // clicon_debug(1, "%s", __FUNCTION__);
- /* Validate that exactly only tag */
- if ((xoutput = xml_child_i_type(xret, 0, CX_ELMNT)) == NULL ||
- strcmp(xml_name(xoutput),"rpc-reply") != 0 ||
- xml_child_nr_type(xret, CX_ELMNT) != 1){
- if (netconf_malformed_message_xml(&xerr, "restconf RPC does not have single input") < 0)
- goto done;
- if ((xe = xpath_first(xerr, "rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
- /* xoutput should now look: 0 */
- /* 9. Translate to restconf RPC data */
- xml_name_set(xoutput, "output");
- /* xoutput should now look: */
-#if 1
- if (debug){
- cbuf *ccc=cbuf_new();
- if (clicon_xml2cbuf(ccc, xoutput, 0, 0) < 0)
- goto done;
- clicon_debug(1, "%s XOUTPUT:%s", __FUNCTION__, cbuf_get(ccc));
- }
-#endif
-
- /* Sanity check of outgoing XML
- * For now, skip outgoing checks.
- * (1) Does not handle properly
- * (2) Uncertain how validation errors should be logged/handled
- */
- if (youtput!=NULL){
- xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */
-#if 0
- if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0)
- goto done;
- if ((ret = xml_yang_validate_all(xoutput, &xerr)) < 0)
- goto done;
- if (ret == 1 &&
- (ret = xml_yang_validate_add(h, xoutput, &xerr)) < 0)
- goto done;
- if (ret == 0){ /* validation failed */
- if ((xe = xpath_first(xerr, "rpc-reply/rpc-error")) == NULL){
- clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
- goto done;
- }
- if (api_return_err(h, r, xe, pretty, use_xml, 0) < 0)
- goto done;
- goto fail;
- }
-#endif
- }
- /* Special case, no yang output (single - or empty body?)
- * RFC 7950 7.14.4
- * If the RPC operation invocation succeeded and no output parameters
- * are returned, the contains a single element
- * RFC 8040 3.6.2
- * If the "rpc" statement has no "output" section, the response message
- * MUST NOT include a message-body and MUST send a "204 No Content"
- * status-line instead.
- */
- isempty = xml_child_nr_type(xoutput, CX_ELMNT) == 0 ||
- (xml_child_nr_type(xoutput, CX_ELMNT) == 1 &&
- (xok = xml_child_i_type(xoutput, 0, CX_ELMNT)) != NULL &&
- strcmp(xml_name(xok),"ok")==0);
- if (isempty) {
- /* Internal error - invalid output from rpc handler */
- FCGX_SetExitStatus(204, r->out); /* OK */
- FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
- FCGX_FPrintF(r->out, "\r\n");
- goto fail;
- }
- /* Clear namespace of parameters */
- x = NULL;
- while ((x = xml_child_each(xoutput, x, CX_ELMNT)) != NULL) {
- if ((xa = xml_find_type(x, NULL, "xmlns", CX_ATTR)) != NULL)
- if (xml_purge(xa) < 0)
- goto done;
- }
- /* Set namespace on output */
- if (xmlns_set(xoutput, NULL, namespace) < 0)
- goto done;
- *xoutputp = xoutput;
- retval = 1;
- done:
- clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
- if (xerr)
- xml_free(xerr);
- return retval;
- fail:
- retval = 0;
- goto done;
-}
-
-/*! 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/
- * 1. Initialize
- * 2. Get rpc module and name from uri (oppath) and find yang spec
- * 3. Build xml tree with user and rpc:
- * 4. Parse input data (arguments):
- * JSON: {"example:input":{"x":0}}
- * XML: 0
- * 5. Translate input args to Netconf RPC, add to xml tree:
- * 42
- * 6. Validate outgoing RPC and fill in default values
- * 4299
- * 7. Send to RPC handler, either local or backend
- * 8. Receive reply from local/backend handler as Netconf RPC
- * 0
- * 9. Translate to restconf RPC data:
- * JSON: {"example:output":{"x":0}}
- * XML: