Stricter incoming RPC sanity checking, error messages may have changed

This commit is contained in:
Olof hagsand 2020-04-07 20:54:24 +02:00
parent f9998c441c
commit ac5637a46a
30 changed files with 163 additions and 99 deletions

View file

@ -24,12 +24,19 @@
## 4.5.0 ## 4.5.0
Expected: May 2020 Expected: May 2020
### API changes on existing protocol/config features (You may have have to change how you use Clixon)
* Stricter incoming RPC sanity checking, error messages may have changed.
### C-API changes on existing features (you may need to change your plugin C-code) ### C-API changes on existing features (you may need to change your plugin C-code)
* CLI * CLI
* `clicon_parse()`: Changed signature due to new cligen error and result handling: * `clicon_parse()`: Changed signature due to new cligen error and result handling:
* Removed: `cli_nomatch()` * Removed: `cli_nomatch()`
### Minor changes
## 4.4.0 ## 4.4.0
5 April 2020 5 April 2020
@ -115,8 +122,6 @@ features include optimized search functions and a repair callback.
### Minor changes ### Minor changes
* Added a compile-time option `MOVE_TRANS_END` which changes the semantics of the transaction_end callback. Instead of being called after a transaction, it is called prior to the target database is installed. This is to ensure that the source and target databases are same as for other transaction callbacks.
* Moved hello example to [clixon-examples](https://github.com/clicon/clixon-examples) * Moved hello example to [clixon-examples](https://github.com/clicon/clixon-examples)
* Sanity check of mandatory key statement for Yang LISTs. * Sanity check of mandatory key statement for Yang LISTs.
* If fails, exit with error message, eg: `Yang error: Sanity check failed: LIST vsDataContainer lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)` * If fails, exit with error message, eg: `Yang error: Sanity check failed: LIST vsDataContainer lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)`

View file

@ -1570,12 +1570,16 @@ from_client_msg(clicon_handle h,
goto done; goto done;
} }
/* Decode msg from client -> xml top (ct) and session id */ /* Decode msg from client -> xml top (ct) and session id */
if (clicon_msg_decode(msg, yspec, &id, &xt) < 0){ if ((ret = clicon_msg_decode(msg, yspec, &id, &xt, &xret)) < 0){
if (netconf_malformed_message(cbret, "XML parse error")< 0) if (netconf_malformed_message(cbret, "XML parse error") < 0)
goto done;
goto reply;
}
if (ret == 0){
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
goto done; goto done;
goto reply; goto reply;
} }
if ((x = xpath_first(xt, NULL, "/rpc")) == NULL){ if ((x = xpath_first(xt, NULL, "/rpc")) == NULL){
if ((x = xpath_first(xt, NULL, "/hello")) != NULL){ if ((x = xpath_first(xt, NULL, "/hello")) != NULL){
if ((ret = from_client_hello(h, x, ce, cbret)) <0) if ((ret = from_client_hello(h, x, ce, cbret)) <0)
@ -1589,12 +1593,6 @@ from_client_msg(clicon_handle h,
} }
} }
ce->ce_id = id; ce->ce_id = id;
/* Populate incoming XML tree with yang -
* should really have been dealt with by decode above
* but it still is needed - test_cli debug test fails
*/
if (xml_bind_yang_rpc(x, yspec, NULL) < 0)
goto done;
if ((ret = xml_yang_validate_rpc(h, x, &xret)) < 0) if ((ret = xml_yang_validate_rpc(h, x, &xret)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){

View file

@ -509,8 +509,6 @@ from_validate_common(clicon_handle h,
* @retval -1 Error - or validation failed * @retval -1 Error - or validation failed
* @retval 0 Validation failed (with cbret set) * @retval 0 Validation failed (with cbret set)
* @retval 1 Validation OK * @retval 1 Validation OK
* @note Need to differentiate between error and validation fail
* (only done for validate_common)
*/ */
int int
candidate_commit(clicon_handle h, candidate_commit(clicon_handle h,

View file

@ -977,7 +977,8 @@ cli_notification_cb(int s,
cxobj *xt = NULL; cxobj *xt = NULL;
cxobj *xe; cxobj *xe;
cxobj *x; cxobj *x;
enum format_enum format = (enum format_enum)arg; enum format_enum format = (enum format_enum)arg;
int ret;
/* get msg (this is the reason this function is called) */ /* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof) < 0) if (clicon_msg_rcv(s, &reply, &eof) < 0)
@ -989,8 +990,13 @@ cli_notification_cb(int s,
event_unreg_fd(s, cli_notification_cb); event_unreg_fd(s, cli_notification_cb);
goto done; goto done;
} }
if (clicon_msg_decode(reply, NULL, NULL, &xt) < 0) /* XXX pass yang_spec */ /* XXX pass yang_spec and use xerr*/
if ((ret = clicon_msg_decode(reply, NULL, NULL, &xt, NULL)) < 0)
goto done; goto done;
if (ret == 0){ /* will not happen since no yspec ^*/
clicon_err(OE_NETCONF, EFAULT, "Notification malformed");
goto done;
}
if ((xe = xpath_first(xt, NULL, "//event")) != NULL){ if ((xe = xpath_first(xt, NULL, "//event")) != NULL){
x = NULL; x = NULL;
while ((x = xml_child_each(xe, x, -1)) != NULL) { while ((x = xml_child_each(xe, x, -1)) != NULL) {

View file

@ -425,6 +425,8 @@ netconf_notification_cb(int s,
clicon_handle h = (clicon_handle)arg; clicon_handle h = (clicon_handle)arg;
yang_stmt *yspec = NULL; yang_stmt *yspec = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
int ret;
cxobj *xerr = NULL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */ /* get msg (this is the reason this function is called) */
@ -439,9 +441,12 @@ netconf_notification_cb(int s,
goto done; goto done;
} }
yspec = clicon_dbspec_yang(h); yspec = clicon_dbspec_yang(h);
if (clicon_msg_decode(reply, yspec, NULL, &xt) < 0) if ((ret = clicon_msg_decode(reply, yspec, NULL, &xt, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ /* XXX use xerr */
clicon_err(OE_NETCONF, EFAULT, "Notification malformed");
goto done;
}
if ((nsc = xml_nsctx_init(NULL, NOTIFICATION_RFC5277_NAMESPACE)) == NULL) if ((nsc = xml_nsctx_init(NULL, NOTIFICATION_RFC5277_NAMESPACE)) == NULL)
goto done; goto done;
if ((xn = xpath_first(xt, nsc, "notification")) == NULL) if ((xn = xpath_first(xt, nsc, "notification")) == NULL)
@ -467,6 +472,8 @@ netconf_notification_cb(int s,
xml_nsctx_free(nsc); xml_nsctx_free(nsc);
if (xt != NULL) if (xt != NULL)
xml_free(xt); xml_free(xt);
if (xerr != NULL)
xml_free(xerr);
if (reply) if (reply)
free(reply); free(reply);
return retval; return retval;

View file

@ -847,8 +847,17 @@ api_operations_post(clicon_handle h,
clicon_log_xml(LOG_DEBUG, xtop, "%s 5. Translate input args:", __FUNCTION__); clicon_log_xml(LOG_DEBUG, xtop, "%s 5. Translate input args:", __FUNCTION__);
#endif #endif
/* 6. Validate outgoing RPC and fill in defaults */ /* 6. Validate outgoing RPC and fill in defaults */
if (xml_bind_yang_rpc(xtop, yspec, NULL) < 0) /* */ if ((ret = xml_bind_yang_rpc(xtop, yspec, &xret)) < 0) /* */
goto done; goto done;
if (ret == 0){
if ((xe = xpath_first(xret, NULL, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if ((ret = xml_yang_validate_rpc(h, xtop, &xret)) < 0) if ((ret = xml_yang_validate_rpc(h, xtop, &xret)) < 0)
goto done; goto done;

View file

@ -161,6 +161,7 @@ restconf_stream_cb(int s,
cxobj *xn; /* notification xml */ cxobj *xn; /* notification xml */
cbuf *cb = NULL; cbuf *cb = NULL;
int pretty = 0; /* XXX should be via arg */ int pretty = 0; /* XXX should be via arg */
int ret;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */ /* get msg (this is the reason this function is called) */
@ -180,8 +181,12 @@ restconf_stream_cb(int s,
clicon_exit_set(); clicon_exit_set();
goto done; goto done;
} }
if (clicon_msg_decode(reply, NULL, NULL, &xtop) < 0) /* XXX pass yang_spec */ if ((ret = clicon_msg_decode(reply, NULL, NULL, &xtop, NULL)) < 0) /* XXX pass yang_spec */
goto done; goto done;
if (ret == 0){
clicon_err(OE_XML, EFAULT, "Invalid notification");
goto done;
}
/* create event */ /* create event */
if ((cb = cbuf_new()) == NULL){ if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new"); clicon_err(OE_PLUGIN, errno, "cbuf_new");

View file

@ -98,10 +98,3 @@
*/ */
#define STATE_ORDERED_BY_SYSTEM #define STATE_ORDERED_BY_SYSTEM
/*! Separate list merge into two separate rounds,
* First round search for duplicates where objects are saved, and a second round where
* actual merge is done. If in same round, in extreme cases that later
* searches are among earlier objects already added
* clixon-4.4
*/
#define XML_MERGE_TWO_ROUNDS

View file

@ -86,6 +86,7 @@
#include <clixon/clixon_regex.h> #include <clixon/clixon_regex.h>
#include <clixon/clixon_path.h> #include <clixon/clixon_path.h>
#include <clixon/clixon_xml_map.h> #include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_bind.h>
#include <clixon/clixon_xml_io.h> #include <clixon/clixon_xml_io.h>
#include <clixon/clixon_validate.h> #include <clixon/clixon_validate.h>
#include <clixon/clixon_datastore.h> #include <clixon/clixon_datastore.h>

View file

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

View file

@ -67,7 +67,7 @@ struct clicon_msg *clicon_msg_encode(uint32_t id, char *format, ...) __attribute
#else #else
struct clicon_msg *clicon_msg_encode(uint32_t id, char *format, ...); struct clicon_msg *clicon_msg_encode(uint32_t id, char *format, ...);
#endif #endif
int clicon_msg_decode(struct clicon_msg *msg, yang_stmt *yspec, uint32_t *id, cxobj **xml); int clicon_msg_decode(struct clicon_msg *msg, yang_stmt *yspec, uint32_t *id, cxobj **xml, cxobj **xerr);
int clicon_connect_unix(clicon_handle h, char *sockpath); int clicon_connect_unix(clicon_handle h, char *sockpath);

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON. This file is part of CLIXON.
@ -102,6 +103,7 @@ int clicon_str2int(const map_str2int *mstab, char *str);
int clicon_str2int_search(const map_str2int *mstab, char *str, int upper); int clicon_str2int_search(const map_str2int *mstab, char *str, int upper);
int nodeid_split(char *nodeid, char **prefix, char **id); int nodeid_split(char *nodeid, char **prefix, char **id);
char *clixon_trim(char *str); char *clixon_trim(char *str);
char *clixon_trim2(char *str, char *trims);
int clicon_strcmp(char *s1, char *s2); int clicon_strcmp(char *s1, char *s2);
#ifndef HAVE_STRNDUP #ifndef HAVE_STRNDUP

View file

@ -120,10 +120,8 @@ enum yang_bind{
YB_NONE=0, /* Dont do Yang binding */ YB_NONE=0, /* Dont do Yang binding */
YB_MODULE, /* Search for matching yang binding among top-level symbols of Yang modules */ YB_MODULE, /* Search for matching yang binding among top-level symbols of Yang modules */
YB_PARENT, /* Assume yang binding of existing parent and match its children by name */ YB_PARENT, /* Assume yang binding of existing parent and match its children by name */
YB_RPC, /* Assume top-level xml is an netconf RPC message (or hello) */
#ifdef NYI
YB_RPC, /* Assume top-level xml is an netconf RPC message */
#endif
}; };
typedef enum yang_bind yang_bind; typedef enum yang_bind yang_bind;

View file

@ -70,7 +70,7 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$
SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \ clixon_string.c clixon_regex.c clixon_handle.c clixon_file.c \
clixon_xml.c clixon_xml_io.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c \ clixon_xml.c clixon_xml_io.c clixon_xml_sort.c clixon_xml_map.c clixon_xml_vec.c \
clixon_json.c \ clixon_xml_bind.c clixon_json.c \
clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_yang_parse_lib.c \ clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_yang_parse_lib.c \
clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \ clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \
clixon_path.c clixon_validate.c \ clixon_path.c clixon_validate.c \

View file

@ -362,6 +362,10 @@ text_modify(clicon_handle h,
} }
} }
if (x1bstr){ if (x1bstr){
/* XXX: Do the type lookup here inline instead, there are more
* cases where we check for types, eg where we now call clixon_trim2
* AND in nacm path where we need proper namespace contexts.
*/
if (check_identityref(x1, x0, x0p, x1bstr, y0) < 0) if (check_identityref(x1, x0, x0p, x1bstr, y0) < 0)
goto done; goto done;
if ((x0b = xml_body_get(x0)) != NULL){ if ((x0b = xml_body_get(x0)) != NULL){
@ -496,7 +500,7 @@ text_modify(clicon_handle h,
if (xml_copy(x1, x0) < 0) if (xml_copy(x1, x0) < 0)
goto done; goto done;
break; break;
} } /* anyxml, anydata */
if (x0==NULL){ if (x0==NULL){
if (op==OP_MERGE && !permit && xnacm){ if (op==OP_MERGE && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)

View file

@ -45,6 +45,10 @@
* quoted-string = (DQUOTE string DQUOTE) / (SQUOTE string SQUOTE) * quoted-string = (DQUOTE string DQUOTE) / (SQUOTE string SQUOTE)
* positive-integer-value = (non-zero-digit DIGIT*) * positive-integer-value = (non-zero-digit DIGIT*)
* identifier = (ALPHA | "_")(ALPHA | DIGIT | "_" | "-" | ".")* * identifier = (ALPHA | "_")(ALPHA | DIGIT | "_" | "-" | ".")*
*
* RFC 8341: 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.
*/ */
%start start %start start

View file

@ -68,6 +68,7 @@
#include "clixon_yang_type.h" #include "clixon_yang_type.h"
#include "clixon_yang_module.h" #include "clixon_yang_module.h"
#include "clixon_xml_sort.h" #include "clixon_xml_sort.h"
#include "clixon_xml_bind.h"
#include "clixon_xml_map.h" #include "clixon_xml_map.h"
#include "clixon_xml_nsctx.h" /* namespace context */ #include "clixon_xml_nsctx.h" /* namespace context */
#include "clixon_netconf_lib.h" #include "clixon_netconf_lib.h"
@ -1249,6 +1250,12 @@ _json_parse(char *str,
break; break;
case YB_NONE: case YB_NONE:
break; break;
case YB_RPC:
if ((ret = xml_bind_yang_rpc(x, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
} }
/* Now find leafs with identityrefs (+transitive) and translate /* Now find leafs with identityrefs (+transitive) and translate
* prefixes in values to XML namespaces */ * prefixes in values to XML namespaces */

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON. This file is part of CLIXON.
@ -351,13 +352,13 @@ nacm_rule_datanode(cxobj *xt,
/* 6b) Either (1) the rule does not have a "rule-type" defined or /* 6b) Either (1) the rule does not have a "rule-type" defined or
(2) the "rule-type" is "data-node" and the "path" matches the (2) the "rule-type" is "data-node" and the "path" matches the
requested data node, action node, or notification node. A requested data node, action node, or notification node. */
path is considered to match if the requested node is the node
specified by the path or is a descendant node of the path.*/
if ((path = xml_find_body(xrule, "path")) == NULL){ if ((path = xml_find_body(xrule, "path")) == NULL){
if (xml_find_body(xrule, "rpc-name") ||xml_find_body(xrule, "notification-name")) if (xml_find_body(xrule, "rpc-name") ||xml_find_body(xrule, "notification-name"))
goto nomatch; goto nomatch;
} }
else
path = clixon_trim2(path, " \t\n");
access_operations = xml_find_body(xrule, "access-operations"); access_operations = xml_find_body(xrule, "access-operations");
switch (access){ switch (access){
case NACM_READ: case NACM_READ:
@ -387,10 +388,17 @@ nacm_rule_datanode(cxobj *xt,
default: default:
break; break;
} }
/* Here module is matched, now check for path if any NYI */ /* Here module is matched, now check for path if any NYI
* A path is considered to match if the requested node is the node
* specified by the path or is a descendant node of the path */
if (path){ if (path){
#if 0
if ((ret = clixon_xml_find_instance_id(xt, yt, xvec, xlen, "%s", path)) == NULL)
goto nomatch;
#else
if ((xpath = xpath_first(xt, nsc, "%s", path)) == NULL) if ((xpath = xpath_first(xt, nsc, "%s", path)) == NULL)
goto nomatch; goto nomatch;
#endif
/* The requested node xr is the node specified by the path or is a /* The requested node xr is the node specified by the path or is a
* descendant node of the path: * descendant node of the path:
* xmatch is one of xvec[] or an ancestor of the xvec[] nodes. * xmatch is one of xvec[] or an ancestor of the xvec[] nodes.

View file

@ -1321,9 +1321,9 @@ api_path_resolve(clixon_path *cplist,
* @retval -1 Error * @retval -1 Error
* @retval 0 Fail * @retval 0 Fail
* @retval 1 OK * @retval 1 OK
* @note: The spec says: prefixes depend on the XML context in which the value occurs. However, * @note: The spec says: prefixes depend on the XML context in which the value occurs.
* canonical prefixes/namespaces are used based on loaded yang modules. * However, canonical prefixes/namespaces are used based on loaded yang modules.
* This means that prefix=NULL is not allowed. * This means that prefix=NULL is not allowed.
* Reasons for fail (retval = 0) are: * Reasons for fail (retval = 0) are:
* - No prefix of identifier (keynames may omit prefix) * - No prefix of identifier (keynames may omit prefix)
* - Prefix does not correspond to existing module. * - Prefix does not correspond to existing module.
@ -1564,7 +1564,8 @@ clixon_xml_find_api_path(cxobj *xt,
/*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg /*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
* *
* Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for example. * Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for
* example.
* @param[in] xt Top xml-tree where to search * @param[in] xt Top xml-tree where to search
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level) * @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use * @param[out] xvec Vector of xml-trees. Vector must be free():d after use
@ -1588,6 +1589,7 @@ clixon_xml_find_api_path(cxobj *xt,
* } * }
* clixon_xvec_free(xvec); * clixon_xvec_free(xvec);
* @endcode * @endcode
* @note canonical namespace contexts are used, seexpath2canonical
* @see clixon_xml_find_api_path for RESTCONF api-paths * @see clixon_xml_find_api_path for RESTCONF api-paths
* @see RFC7950 Sec 9.13 * @see RFC7950 Sec 9.13
*/ */

View file

@ -59,6 +59,7 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/un.h> #include <sys/un.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <assert.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -170,15 +171,21 @@ clicon_msg_encode(uint32_t id,
* @param[in] yspec Yang specification, (can be NULL) * @param[in] yspec Yang specification, (can be NULL)
* @param[out] id Session id * @param[out] id Session id
* @param[out] xml XML parse tree * @param[out] xml XML parse tree
* @param[out] xerr Reason for failure (yang assignment not made) if retval =0
* @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error
*/ */
int int
clicon_msg_decode(struct clicon_msg *msg, clicon_msg_decode(struct clicon_msg *msg,
yang_stmt *yspec, yang_stmt *yspec,
uint32_t *id, uint32_t *id,
cxobj **xml) cxobj **xml,
cxobj **xerr)
{ {
int retval = -1; int retval = -1;
char *xmlstr; char *xmlstr;
int ret;
/* hdr */ /* hdr */
if (id) if (id)
@ -186,11 +193,16 @@ clicon_msg_decode(struct clicon_msg *msg,
/* body */ /* body */
xmlstr = msg->op_body; xmlstr = msg->op_body;
clicon_debug(1, "%s %s", __FUNCTION__, xmlstr); clicon_debug(1, "%s %s", __FUNCTION__, xmlstr);
if (clixon_xml_parse_string(xmlstr, yspec?YB_MODULE:YB_NONE, yspec, xml, NULL) < 0) if ((ret = clixon_xml_parse_string(xmlstr, yspec?YB_RPC:YB_NONE, yspec, xml, xerr)) < 0)
goto done; goto done;
retval = 0; if (ret == 0)
goto fail;
retval = 1;
done: done:
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/*! Open local connection using unix domain sockets /*! Open local connection using unix domain sockets

View file

@ -76,7 +76,7 @@
#include "clixon_stream.h" #include "clixon_stream.h"
#include "clixon_err_string.h" #include "clixon_err_string.h"
#include "clixon_xml_nsctx.h" #include "clixon_xml_nsctx.h"
#include "clixon_xml_map.h" #include "clixon_xml_bind.h"
#include "clixon_xml_sort.h" #include "clixon_xml_sort.h"
#include "clixon_xml_io.h" #include "clixon_xml_io.h"
#include "clixon_netconf_lib.h" #include "clixon_netconf_lib.h"

View file

@ -2,7 +2,9 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC
This file is part of CLIXON. This file is part of CLIXON.
@ -692,6 +694,29 @@ clixon_trim(char *str)
return s; return s;
} }
/*! Trim blanks from front and end of a string, return new string
* @param[in] str
* @param[in] trims Characters to trim: a vector of characters
* @retval s Pointer into existing str after trimming blanks
*/
char *
clixon_trim2(char *str,
char *trims)
{
char *s = str;
int i;
while (strlen(s) && index(trims, s[0])) /* trim from front */
s++;
for (i=strlen(s)-1; i>=0; i--){ /* trim from rear */
if (index(trims, s[i]))
s[i] = '\0';
else
break;
}
return s;
}
/*! check string equals (NULL is equal) /*! check string equals (NULL is equal)
* @param[in] s1 String 1 * @param[in] s1 String 1
* @param[in] s2 String 2 * @param[in] s2 String 2

View file

@ -1090,14 +1090,10 @@ xml_yang_validate_all(clicon_handle h,
cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt)); cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt));
if ((xp = xml_parent(xt)) != NULL) if ((xp = xml_parent(xt)) != NULL)
cprintf(cb, " with parent: %s", xml_name(xp)); cprintf(cb, " with parent: %s", xml_name(xp));
if (namespace){ if (namespace)
cprintf(cb, " in namespace: %s", namespace); cprintf(cb, " in namespace: %s", namespace);
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0) if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done; goto done;
}
else
if (netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
goto fail; goto fail;
} }
if (yang_config(ys) != 0){ if (yang_config(ys) != 0){

View file

@ -66,9 +66,9 @@
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_xml.h" #include "clixon_xml.h"
#include "clixon_options.h" /* xml_bind_yang */ #include "clixon_options.h"
#include "clixon_yang_module.h" #include "clixon_yang_module.h"
#include "clixon_xml_map.h" /* xml_bind_yang */ #include "clixon_xml_bind.h"
#include "clixon_xml_vec.h" #include "clixon_xml_vec.h"
#include "clixon_xml_sort.h" #include "clixon_xml_sort.h"
#include "clixon_xml_nsctx.h" #include "clixon_xml_nsctx.h"
@ -455,7 +455,13 @@ _xml_parse(const char *str,
if (ret == 0) if (ret == 0)
failed++; failed++;
break; break;
} case YB_RPC:
if ((ret = xml_bind_yang_rpc(x, yspec, xerr)) < 0)
goto done;
if (ret == 0)
failed++;
break;
} /* switch */
} }
if (failed) if (failed)
goto fail; goto fail;
@ -601,7 +607,7 @@ clixon_xml_parse_file(int fd,
* @param[in] yb How to bind yang to XML top-level when parsing * @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Yang specification, or NULL * @param[in] yspec Yang specification, or NULL
* @param[in,out] xt Pointer to XML parse tree. If empty will be created. * @param[in,out] xt Pointer to XML parse tree. If empty will be created.
* @param[out] xerr Reason for failure (yang assignment not made) * @param[out] xerr Reason for failure (yang assignment not made) if retval = 0
* @retval 1 Parse OK and all yang assignment made * @retval 1 Parse OK and all yang assignment made
* @retval 0 Parse OK but yang assigment not made (or only partial) * @retval 0 Parse OK but yang assigment not made (or only partial)
* @retval -1 Error with clicon_err called. Includes parse error * @retval -1 Error with clicon_err called. Includes parse error

View file

@ -1304,14 +1304,12 @@ xml_merge1(cxobj *x0, /* the target */
char *x1bstr; /* mod body string */ char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */ yang_stmt *yc; /* yang child */
cbuf *cbr = NULL; /* Reason buffer */ cbuf *cbr = NULL; /* Reason buffer */
#ifdef XML_MERGE_TWO_ROUNDS
int i; int i;
struct { struct {
cxobj *w_x0c; cxobj *w_x0c;
cxobj *w_x1c; cxobj *w_x1c;
yang_stmt *w_yc; yang_stmt *w_yc;
} *second_wave = NULL; } *second_wave = NULL;
#endif
assert(x1 && xml_type(x1) == CX_ELMNT); assert(x1 && xml_type(x1) == CX_ELMNT);
assert(y0); assert(y0);
@ -1347,13 +1345,11 @@ xml_merge1(cxobj *x0, /* the target */
} }
if (assign_namespaces(x1, x0, x0p) < 0) if (assign_namespaces(x1, x0, x0p) < 0)
goto done; goto done;
#ifdef XML_MERGE_TWO_ROUNDS
if ((second_wave = calloc(xml_child_nr(x1), sizeof(*second_wave))) == NULL){ if ((second_wave = calloc(xml_child_nr(x1), sizeof(*second_wave))) == NULL){
clicon_err(OE_UNIX, errno, "calloc"); clicon_err(OE_UNIX, errno, "calloc");
goto done; goto done;
} }
i = 0; i = 0;
#endif
/* Loop through children of the modification tree */ /* Loop through children of the modification tree */
x1c = NULL; x1c = NULL;
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
@ -1377,7 +1373,6 @@ xml_merge1(cxobj *x0, /* the target */
x0c = NULL; x0c = NULL;
if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) if (yc && match_base_child(x0, x1c, yc, &x0c) < 0)
goto done; goto done;
#ifdef XML_MERGE_TWO_ROUNDS
/* Save x0c, x1c, yc and merge in second wave, so that x1c entries "interfer" /* Save x0c, x1c, yc and merge in second wave, so that x1c entries "interfer"
* with itself, ie that later searches are among earlier objects already added * with itself, ie that later searches are among earlier objects already added
* to x0 */ * to x0 */
@ -1385,14 +1380,7 @@ xml_merge1(cxobj *x0, /* the target */
second_wave[i].w_x1c = x1c; second_wave[i].w_x1c = x1c;
second_wave[i].w_yc = yc; second_wave[i].w_yc = yc;
i++; i++;
#else
if (xml_merge1(x0c, yc, x0, x1c, reason) < 0)
goto done;
if (*reason != NULL)
goto ok;
#endif
} /* while */ } /* while */
#ifdef XML_MERGE_TWO_ROUNDS
/* Second run where actual merging is done /* Second run where actual merging is done
* Loop through children of the modification tree */ * Loop through children of the modification tree */
x1c = NULL; x1c = NULL;
@ -1408,7 +1396,6 @@ xml_merge1(cxobj *x0, /* the target */
goto ok; goto ok;
i++; i++;
} }
#endif
if (xml_parent(x0) == NULL && if (xml_parent(x0) == NULL &&
xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0) xml_insert(x0p, x0, INS_LAST, NULL, NULL) < 0)
goto done; goto done;
@ -1416,10 +1403,8 @@ xml_merge1(cxobj *x0, /* the target */
ok: ok:
retval = 0; retval = 0;
done: done:
#ifdef XML_MERGE_TWO_ROUNDS
if (second_wave) if (second_wave)
free(second_wave); free(second_wave);
#endif
if (cbr) if (cbr)
cbuf_free(cbr); cbuf_free(cbr);
return retval; return retval;

View file

@ -660,7 +660,8 @@ cv_validate1(clicon_handle h,
if (strcmp(restype, "enumeration") == 0){ if (strcmp(restype, "enumeration") == 0){
found = 0; found = 0;
yi = NULL; yi = NULL;
if (str != NULL) if (str != NULL) {
str = clixon_trim2(str, " \t\n"); /* May be misplaced, strip earlier? */
while ((yi = yn_each(yrestype, yi)) != NULL){ while ((yi = yn_each(yrestype, yi)) != NULL){
if (yang_keyword_get(yi) != Y_ENUM) if (yang_keyword_get(yi) != Y_ENUM)
continue; continue;
@ -669,6 +670,7 @@ cv_validate1(clicon_handle h,
break; break;
} }
} }
}
if (!found){ if (!found){
if (reason) if (reason)
*reason = cligen_reason("'%s' does not match enumeration", str); *reason = cligen_reason("'%s' does not match enumeration", str);
@ -680,7 +682,7 @@ cv_validate1(clicon_handle h,
* of the names of the bits that are set. A zero-length string thus * of the names of the bits that are set. A zero-length string thus
* represents a value where no bits are set. * represents a value where no bits are set.
*/ */
str = clixon_trim2(str, " \t\n"); /* May be misplaced, strip earlier? */
nvec = 0; nvec = 0;
if ((vec = clicon_strsep(str, " \t", &nvec)) == NULL) if ((vec = clicon_strsep(str, " \t", &nvec)) == NULL)
goto done; goto done;

View file

@ -283,7 +283,7 @@ expectpart "$(curl -s -i -X DELETE http://localhost/restconf/data/example:crypt
# 2. set identity in other module with restconf , read it with restconf and netconf # 2. set identity in other module with restconf , read it with restconf and netconf
new "restconf add POST instead of PUT (should fail)" new "restconf add POST instead of PUT (should fail)"
expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Missing matching yang node"}}}' expectpart "$(curl -s -i -X POST -H "Content-Type: application/yang-data+json" http://localhost/restconf/data/example:crypto -d '{"example:crypto":"example-des:des3"}')" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: crypto with parent: crypto in namespace: urn:example:my-crypto"}}}'
# Alternative error: # Alternative error:
#'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}' #'{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"crypto"},"error-severity":"error","error-message":"Leaf contains sub-element"}}}'

View file

@ -6,17 +6,6 @@
# @see test_nacm.sh is slightly modified - this follows the RFC more closely # @see test_nacm.sh is slightly modified - this follows the RFC more closely
# See RFC 8341 A.1 and A.2 # See RFC 8341 A.1 and A.2
# Note: use clixon-example instead of ietf-netconf-monitoring since the latter is # Note: use clixon-example instead of ietf-netconf-monitoring since the latter is
# Tests for
# deny-ncm: This rule prevents the "guest" group from reading any
# monitoring information in the "clixon-example" YANG
# module.
# permit-ncm: This rule allows the "limited" group to read the
# "clixon-example" YANG module.
# permit-exec: This rule allows the "limited" group to invoke any
# protocol operation supported by the server.
# permit-all: This rule allows the "admin" group complete access to
# all content in the server. No subsequent rule will match for the
# "admin" group because of this module rule
# Magic line must be first in script (see README.md) # Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi

View file

@ -123,9 +123,10 @@ new "restconf empty rpc, default media type should fail (JSON)"
expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type' expectpart "$(curl -si -X POST -H "Accept: application/yang-data+json" -d {\"clixon-example:input\":null} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 415 Unsupported Media Type'
new "restconf empty rpc with extra args (should fail)" new "restconf empty rpc with extra args (should fail)"
expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: empty in namespace: urn:example:clixon"}}} ' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":{\"extra\":null}} http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Unrecognized parameter: extra in rpc: empty"}}}'
new "restconf debug rpc" # Irritiating to get debugs on the terminal
#new "restconf debug rpc"
#expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content" #expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-lib:input\":{\"level\":0}} http://localhost/restconf/operations/clixon-lib:debug)" 0 "HTTP/1.1 204 No Content"
new "restconf get empty config + state json" new "restconf get empty config + state json"
@ -243,19 +244,19 @@ expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"
' '
new "restconf rpc using POST json wrong" new "restconf rpc using POST json wrong"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}} ' expectpart "$(curl -si -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"wrongelement"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: wrongelement with parent: example in namespace: urn:example:clixon"}}}'
new "restconf rpc non-existing rpc without namespace" new "restconf rpc non-existing rpc without namespace"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
new "restconf rpc non-existing rpc" new "restconf rpc non-existing rpc"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:kalle)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"kalle"},"error-severity":"error","error-message":"RPC not defined"}}'
new "restconf rpc missing name" new "restconf rpc missing name"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations)" 0 'HTTP/1.1 412 Precondition Failed' '{"ietf-restconf:errors":{"error":{"error-type":"protocol","error-tag":"operation-failed","error-severity":"error","error-message":"Operation name expected"}}}'
new "restconf rpc missing input" new "restconf rpc missing input"
expecteq "$(curl -s -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"rpc","error-tag":"malformed-message","error-severity":"error","error-message":"restconf RPC does not have input statement"}}}'
new "restconf rpc using POST xml" new "restconf rpc using POST xml"
ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example) ret=$(curl -s -X POST -H "Content-Type: application/yang-data+json" -H "Accept: application/yang-data+xml" -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)

View file

@ -114,7 +114,7 @@ if [ -z "$match" ]; then
fi fi
new "restconf empty rpc with input x" new "restconf empty rpc with input x"
expectpart "$(curl -iss -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: x with parent: empty in namespace: urn:example:clixon"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":0}}' http://localhost/restconf/operations/clixon-example:empty)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Unrecognized parameter: x in rpc: empty"}}}'
# cornercase: optional has yang input/output sections but test without body # cornercase: optional has yang input/output sections but test without body
new "restconf optional rpc with null input and output" new "restconf optional rpc with null input and output"
@ -129,7 +129,7 @@ new "restconf omit mandatory"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":null}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"x"},"error-severity":"error","error-message":"Mandatory variable"}}} '
new "restconf add extra" new "restconf add extra"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"unknown-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: example in namespace: urn:example:clixon"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0","extra":"0"}}' http://localhost/restconf/operations/clixon-example:example)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"bad-element","error-info":{"bad-element":"extra"},"error-severity":"error","error-message":"Failed to find YANG spec of XML node: extra with parent: example in namespace: urn:example:clixon"}}}'
new "restconf wrong method" new "restconf wrong method"
expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}} ' expectpart "$(curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-example:input":{"x":"0"}}' http://localhost/restconf/operations/clixon-example:wrong)" 0 'HTTP/1.1 400 Bad Request' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"missing-element","error-info":{"bad-element":"wrong"},"error-severity":"error","error-message":"RPC not defined"}}} '