* RPC replies now verified with YANG

* Stricter checking of outgoing RPC replies from server
  * See [RPC output not verified by yang](https://github.com/clicon/clixon/issues/283)
  * This lead to some corrections of RPC replies in system code
This commit is contained in:
Olof hagsand 2021-11-16 22:09:16 +01:00
parent cfe1f2936e
commit 0626de9431
11 changed files with 197 additions and 26 deletions

View file

@ -66,6 +66,7 @@
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_yang_module.h"
#include "clixon_validate.h"
#include "clixon_plugin.h"
/*
@ -1093,13 +1094,14 @@ rpc_callback_delete_all(clicon_handle h)
/*! Search RPC callbacks and invoke if XML match with tag
*
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[out] cbret Return XML (as string in CLIgen buffer), error or OK
* @param[in] arg Domain-speific arg (eg client_entry)
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[in] arg Domain-speific arg (eg client_entry)
* @param[out] nr Number of callbacks handled: 0, 1, n (retval = 1)
* @param[out] cbret Return XML (as string in CLIgen buffer), error or OK
* @retval -1 Error
* @retval 0 OK, not found handler.
* @retval n OK, <n> handler called
* @retval 0 Failed, error return in cbret
* @retval 1 OK, see nr
* @see rpc_callback_register which register a callback function
* @note that several callbacks can be registered. They need to cooperate on
* return values, ie if one writes cbret, the other needs to handle that by
@ -1108,8 +1110,9 @@ rpc_callback_delete_all(clicon_handle h)
int
rpc_callback_call(clicon_handle h,
cxobj *xe,
cbuf *cbret,
void *arg)
void *arg,
int *nrp,
cbuf *cbret)
{
int retval = -1;
rpc_callback_t *rc;
@ -1119,6 +1122,7 @@ rpc_callback_call(clicon_handle h,
int nr = 0; /* How many callbacks */
plugin_module_struct *ms = plugin_module_struct_get(h);
plugin_context_t *pc = NULL;
int ret;
if (ms == NULL){
clicon_err(OE_PLUGIN, EINVAL, "plugin module not initialized");
@ -1148,12 +1152,22 @@ rpc_callback_call(clicon_handle h,
}
rc = NEXTQ(rpc_callback_t *, rc);
} while (rc != ms->ms_rpc_callbacks);
retval = nr; /* 0: none found, >0 nr of handlers called */
if (nr){
if ((ret = rpc_reply_check(h, name, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
*nrp = nr;
retval = 1; /* 0: none found, >0 nr of handlers called */
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pc)
free(pc);
return retval;
fail:
retval = 0;
goto done;
}
/*--------------------------------------------------------------------

View file

@ -69,6 +69,7 @@
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_data.h"
#include "clixon_netconf_lib.h"
#include "clixon_options.h"
#include "clixon_xml_nsctx.h"
@ -78,6 +79,7 @@
#include "clixon_yang_module.h"
#include "clixon_yang_type.h"
#include "clixon_xml_map.h"
#include "clixon_xml_bind.h"
#include "clixon_validate.h"
/*! Validate xml node of type leafref, ensure the value is one of that path's reference
@ -419,6 +421,67 @@ xml_yang_validate_rpc(clicon_handle h,
goto done;
}
int
xml_yang_validate_rpc_reply(clicon_handle h,
cxobj *xrpc,
cxobj **xret)
{
int retval = -1;
yang_stmt *yn=NULL; /* rpc name */
cxobj *xn; /* rpc name */
char *rpcprefix;
char *namespace = NULL;
int ret;
if (strcmp(xml_name(xrpc), "rpc-reply")){
clicon_err(OE_XML, EINVAL, "Expected RPC");
goto done;
}
rpcprefix = xml_prefix(xrpc);
if (xml2ns(xrpc, rpcprefix, &namespace) < 0)
goto done;
/* Only accept resolved NETCONF base namespace */
if (namespace == NULL || strcmp(namespace, NETCONF_BASE_NAMESPACE) != 0){
if (xret && netconf_unknown_namespace_xml(xret, "protocol",
rpcprefix?rpcprefix:"null",
"No appropriate namespace associated with prefix")< 0)
goto done;
goto fail;
}
xn = NULL;
/* xn is name of rpc, ie <rcp><xn/></rpc> */
while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) {
/* OK and rpc-error are not explicit in ietf-netconf.yang. Why are they hardcoded ? */
if (strcmp(xml_name(xn), "ok") == 0 || strcmp(xml_name(xn), "rpc-error") == 0){
continue;
}
if ((yn = xml_spec(xn)) == NULL){
if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xn), NULL) < 0)
goto done;
goto fail;
}
if ((ret = xml_yang_validate_all(h, xn, xret)) < 0)
goto done; /* error or validation fail */
if (ret == 0)
goto fail;
if ((ret = xml_yang_validate_add(h, xn, xret)) < 0)
goto done; /* error or validation fail */
if (ret == 0)
goto fail;
if (xml_default_recurse(xn, 0) < 0)
goto done;
}
// ok: /* pass validation */
retval = 1;
done:
return retval;
fail:
if (xret && *xret && clixon_xml_attr_copy(xrpc, *xret, "message-id") < 0)
goto done;
retval = 0;
goto done;
}
/*! Check if an xml node is a part of a choice and have >1 siblings
* @param[in] xt XML node to be validated
* @param[in] yt xt:s yang statement
@ -1405,3 +1468,64 @@ xml_yang_validate_all_top(clicon_handle h,
return ret;
return 1;
}
/*! Check validity of outgoing RPC
*
* Rewrite return message if errors
* @param[in,out] cbret
* @note Parses cbret which seems one time too many
*/
int
rpc_reply_check(clicon_handle h,
char *rpcname,
cbuf *cbret)
{
int retval = -1;
cxobj *x = NULL;
cxobj *xret = NULL;
int ret;
yang_stmt *yspec;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec9");
goto done;
}
/* Validate outgoing RPC */
if ((ret = clixon_xml_parse_string(cbuf_get(cbret), YB_NONE, NULL, &x, NULL)) < 0)
goto done;
if (xml_child_nr(x) == 0){
cbuf_reset(cbret);
if (netconf_operation_failed(cbret, "application",
"Internal error: Outgoing reply is empty")< 0)
goto done;
goto fail;
}
if (xml_rootchild(x, 0, &x) < 0)
goto done;
if ((ret = xml_bind_yang_rpc_reply(x, rpcname, yspec, &xret)) < 0)
goto done;
if (ret == 0){
cbuf_reset(cbret);
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
goto done;
goto fail;
}
if ((ret = xml_yang_validate_rpc_reply(h, x, &xret)) < 0)
goto done;
if (ret == 0){
cbuf_reset(cbret);
if (clicon_xml2cbuf(cbret, xret, 0, 0, -1) < 0)
goto done;
goto fail;
}
retval = 1;
done:
if (x)
xml_free(x);
if (xret)
xml_free(xret);
return retval;
fail:
retval = 0;
goto done;
}

View file

@ -521,11 +521,11 @@ xml_bind_yang0(cxobj *xt,
* @retval -1 Error
* The
* @code
* if (xml_bind_yang_rpc(x, NULL) < 0)
* if ((ret = xml_bind_yang_rpc(x, NULL, &xerr)) < 0)
* err;
* @endcode
* @see xml_bind_yang For other generic cases
* @see xml_bind_yang_rpc_reply
* @see xml_bind_yang_rpc_reply
*/
int
xml_bind_yang_rpc(cxobj *xrpc,
@ -666,7 +666,7 @@ xml_bind_yang_rpc(cxobj *xrpc,
* @retval -1 Error
*
* @code
* if (xml_bind_yang_rpc_reply(x, "get-config", yspec, name) < 0)
* if ((ret = xml_bind_yang_rpc_reply(x, "get-config", yspec, &xerr)) < 0)
* err;
* @endcode
* @see xml_bind_yang For other generic cases
@ -686,6 +686,7 @@ xml_bind_yang_rpc_reply(cxobj *xrpc,
cxobj *xerr1 = NULL;
char *opname;
cbuf *cberr = NULL;
cxobj *xc;
opname = xml_name(xrpc);
if (strcmp(opname, "rpc-reply")){
@ -716,6 +717,13 @@ xml_bind_yang_rpc_reply(cxobj *xrpc,
}
if (yo != NULL){
xml_spec_set(xrpc, yo);
/* Special case for ok and rpc-error */
if ((xc = xml_child_i_type(xrpc, 0, CX_ELMNT)) != NULL &&
(strcmp(xml_name(xc),"rpc-error") == 0
|| strcmp(xml_name(xc),"ok") == 0
)){
goto ok;
}
/* Use a temporary xml error tree since it is stringified in the original error on error */
if ((ret = xml_bind_yang(xrpc, YB_PARENT, NULL, &xerr1)) < 0)
goto done;
@ -732,6 +740,7 @@ xml_bind_yang_rpc_reply(cxobj *xrpc,
goto fail;
}
}
ok:
retval = 1;
done:
if (cberr)