clixon/lib/src/clixon_validate.c
Olof hagsand 9e54f0602f Changed ca_errmsg callback to a more generic variant
Includes all error, log and debug messages
See [Customized NETCONF error message](https://github.com/clicon/clixon/issues/454)
2024-01-05 16:41:53 +01:00

1481 lines
48 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
Copyright (C) 2017-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Check YANG validation
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_debug.h"
#include "clixon_data.h"
#include "clixon_netconf_lib.h"
#include "clixon_options.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_io.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_yang_module.h"
#include "clixon_yang_type.h"
#include "clixon_yang_schema_mount.h"
#include "clixon_xml_default.h"
#include "clixon_xml_map.h"
#include "clixon_xml_bind.h"
#include "clixon_validate_minmax.h"
#include "clixon_validate.h"
/*! Validate xml node of type leafref, ensure the value is one of that path's reference
*
* @param[in] xt XML leaf node of type leafref
* @param[in] ys Yang spec of leaf
* @param[in] ytype Yang type statement belonging to the XML node
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* From rfc7950
* Sec 9.9:
* The leafref built-in type is restricted to the value space of some
* leaf or leaf-list node in the schema tree and optionally further
* restricted by corresponding instance nodes in the data tree. The
* "path" substatement (Section 9.9.2) is used to identify the referred
* leaf or leaf-list node in the schema tree. The value space of the
* referring node is the value space of the referred node.
*
* If the "require-instance" property (Section 9.9.3) is "true", there
* MUST exist a node in the data tree, or a node with a default value in
* use (see Sections 7.6.1 and 7.7.2), of the referred schema tree leaf
* or leaf-list node with the same value as the leafref value in a valid
* data tree.
* Sec 9.9.2:
* The "path" XPath expression is evaluated in the following context,
* in addition to the definition in Section 6.4.1:
* o If the "path" statement is defined within a typedef, the context
* node is the leaf or leaf-list node in the data tree that
* references the typedef. (ie ys)
* o Otherwise, the context node is the node in the data tree for which
* the "path" statement is defined. (ie ys)
*
*/
static int
validate_leafref(cxobj *xt,
yang_stmt *ys,
yang_stmt *ytype,
cxobj **xret)
{
int retval = -1;
yang_stmt *ypath;
yang_stmt *yreqi;
cxobj **xvec = NULL;
cxobj *x;
int i;
size_t xlen = 0;
char *leafrefbody;
char *leafbody;
cvec *nsc = NULL;
cbuf *cberr = NULL;
char *path_arg;
yang_stmt *ymod;
cg_var *cv;
int require_instance = 1;
/* require instance */
if ((yreqi = yang_find(ytype, Y_REQUIRE_INSTANCE, NULL)) != NULL){
if ((cv = yang_cv_get(yreqi)) != NULL) /* shouldnt happen */
require_instance = cv_bool_get(cv);
}
/* Find referred XML instances */
if (require_instance == 0)
goto ok;
if ((ypath = yang_find(ytype, Y_PATH, NULL)) == NULL){
if ((cberr = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Leafref requires path statement");
if (xret && netconf_missing_element_xml(xret, "application",
yang_argument_get(ytype),
cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((path_arg = yang_argument_get(ypath)) == NULL){
clixon_err(OE_YANG, 0, "No argument for Y_PATH");
goto done;
}
if ((leafrefbody = xml_body(xt)) == NULL)
goto ok;
if (xml_nsctx_yang(ys, &nsc) < 0)
goto done;
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, path_arg) < 0)
goto done;
for (i = 0; i < xlen; i++) {
x = xvec[i];
if ((leafbody = xml_body(x)) == NULL)
continue;
if (strcmp(leafbody, leafrefbody) == 0)
break;
}
if (i==xlen){
if ((cberr = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
ymod = ys_module(ys);
cprintf(cberr, "Leafref validation failed: No leaf %s matching path %s in %s.yang:%d",
leafrefbody,
path_arg,
yang_argument_get(ymod),
yang_linenum_get(ys));
if (xret && netconf_bad_element_xml(xret, "application", leafrefbody, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
ok:
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
if (nsc)
xml_nsctx_free(nsc);
if (xvec)
free(xvec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate xml node of type identityref, ensure value is a defined identity
*
* Check if a given node has value derived from base identity. This is
* a run-time check necessary when validating eg netconf.
* Valid values for an identityref are any identities derived from all
* the identityref's base identities.
* Example:
* b0 --> b1 --> b2 (b1 & b2 are derived)
* identityref b2
* base b0;
* This function does: derived_from(b2, b0);
* @param[in] xt XML leaf node of type identityref
* @param[in] ys Yang spec of leaf
* @param[in] ytype Yang type field of type identityref
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* @see ys_populate_identity where the derived types are set
* @see yang_augment_node
* @see RFC7950 Sec 9.10.2:
* @see xp_function_derived_from similar code other context
*/
static int
validate_identityref(cxobj *xt,
yang_stmt *ys,
yang_stmt *ytype,
cxobj **xret)
{
int retval = -1;
char *node = NULL;
char *idref = NULL;
yang_stmt *ybaseref; /* This is the type's base reference */
yang_stmt *ybaseid;
char *prefix = NULL;
char *id = NULL;
cbuf *cberr = NULL;
cbuf *cb = NULL;
cvec *idrefvec; /* Derived identityref list: (module:id)**/
yang_stmt *ymod;
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((cberr = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* Get idref value. Then see if this value is derived from ytype.
*/
if ((node = xml_body(xt)) == NULL){ /* It may not be empty */
if (xret && netconf_bad_element_xml(xret, "application", xml_name(xt), "Identityref should not be empty") < 0)
goto done;
goto fail;
}
if (nodeid_split(node, &prefix, &id) < 0)
goto done;
/* This is the type's base reference */
if ((ybaseref = yang_find(ytype, Y_BASE, NULL)) == NULL){
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ytype), "Identityref validation failed, no base") < 0)
goto done;
goto fail;
}
/* This is the actual base identity */
if ((ybaseid = yang_find_identity(ybaseref, yang_argument_get(ybaseref))) == NULL){
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(ybaseref), "Identityref validation failed, no base identity") < 0)
goto done;
goto fail;
}
/* idref from prefix:id to module:id */
if (prefix == NULL)
ymod = ys_module(ys);
else{ /* from prefix to name */
ymod = yang_find_module_by_prefix_yspec(ys_spec(ys), prefix);
}
if (ymod == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d",
node,
yang_argument_get(ybaseid),
yang_argument_get(ys_module(ybaseid)),
yang_linenum_get(ybaseid));
if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
cprintf(cb, "%s:%s", yang_argument_get(ymod), id);
idref = cbuf_get(cb);
/* Here check if node is in the derived node list of the base identity
* The derived node list is a cvec computed XXX
*/
idrefvec = yang_cvec_get(ybaseid);
if (cvec_find(idrefvec, idref) == NULL){
cprintf(cberr, "Identityref validation failed, %s not derived from %s in %s.yang:%d",
node,
yang_argument_get(ybaseid),
yang_argument_get(ys_module(ybaseid)),
yang_linenum_get(ybaseid));
if (xret && netconf_operation_failed_xml(xret, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
if (cb)
cbuf_free(cb);
if (id)
free(id);
if (prefix)
free(prefix);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate an RPC node
*
* @param[in] h Clixon handle
* @param[in] xrpc XML node to be validated
* @param[in] expanddefault
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed
* @retval -1 Error
* rfc7950
* 7.14.2
* If a leaf in the input tree has a "mandatory" statement with the
* value "true", the leaf MUST be present in an RPC invocation.
*
* If a leaf in the input tree has a default value, the server MUST use
* this value in the same cases as those described in Section 7.6.1. In
* these cases, the server MUST operationally behave as if the leaf was
* present in the RPC invocation with the default value as its value.
*
* If a leaf-list in the input tree has one or more default values, the
* server MUST use these values in the same cases as those described in
* Section 7.7.2. In these cases, the server MUST operationally behave
* as if the leaf-list was present in the RPC invocation with the
* default values as its values.
*
* Since the input tree is not part of any datastore, all "config"
* statements for nodes in the input tree are ignored.
*
* If any node has a "when" statement that would evaluate to "false",
* then this node MUST NOT be present in the input tree.
*
* 7.14.4
* Input parameters are encoded as child XML elements to the rpc node's
* XML element, in the same order as they are defined within the "input"
* statement.
*
* If the RPC operation invocation succeeded and no output parameters
* are returned, the <rpc-reply> contains a single <ok/> element defined
* in [RFC6241]. If output parameters are returned, they are encoded as
* child elements to the <rpc-reply> element defined in [RFC6241], in
* the same order as they are defined within the "output" statement.
* @see xml_yang_validate_all
* @note Should need a variant accepting cxobj **xret
*/
int
xml_yang_validate_rpc(clixon_handle h,
cxobj *xrpc,
int expanddefault,
cxobj **xret)
{
int retval = -1;
cxobj *xn; /* rpc name */
char *rpcprefix;
char *namespace = NULL;
int ret;
if (strcmp(xml_name(xrpc), "rpc")){
clixon_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, "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) {
if (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 (expanddefault && 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;
}
int
xml_yang_validate_rpc_reply(clixon_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")){
clixon_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 choice node xp has children in same case
*
* @param[in] xp XML choice node to be validated
* @param[in] ytchoice Yang spec of choice
* @param[in] ytcase Yang spec of case, optional
* @param[in] xt XML child
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* From RFC7950 Sec 7.9.3
* 1. Default case, the default if no child nodes from any of the choice's cases exist
* 2. Default for child nodes under a case are only used if one of the nodes under that case
* is present
*/
static int
check_choice(cxobj *xp,
yang_stmt *ytchoice,
yang_stmt *ytcase,
cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
cxobj *x;
yang_stmt *y;
yang_stmt *yp;
x = NULL; /* Find a child with same yang spec */
while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
if (x == xt)
continue;
y = xml_spec(x);
if (y == yt) /* eg same list */
continue;
yp = yang_parent_get(y);
switch (yang_keyword_get(yp)){
case Y_CASE:
if (yang_parent_get(yp) != ytchoice) /* Not same choice (not relevant) */
continue;
if (yp == ytcase) /* same choice but different case */
continue;
break;
case Y_CHOICE:
if (yp != ytchoice) /* Not same choice (not relevant) */
continue;
break;
default:
continue; /* not choice */
break;
}
if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0)
goto done;
goto fail;
} /* while */
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Check if an xml node xt 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
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* Check if xt is part of valid choice
* XXX does not check: xt is choice and has n children, but there exists a default child
*/
static int
check_choice_child(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
yang_stmt *ytp; /* yt:s parent */
yang_stmt *ytcase = NULL; /* yt:s parent case if any */
yang_stmt *ytchoice = NULL;
int ret;
cxobj *xp;
#if 0
cxobj *x;
yang_stmt *yp;
yang_stmt *y;
#endif
if ((ytp = yang_parent_get(yt)) == NULL)
goto ok;
/* Return OK if xt is not choice */
switch (yang_keyword_get(ytp)){
case Y_CASE:
ytcase = ytp;
ytchoice = yang_parent_get(ytp);
break;
case Y_CHOICE:
ytchoice = ytp;
break;
default:
goto ok; /* Not choice */
break;
}
if ((xp = xml_parent(xt)) == NULL)
goto ok;
#if 1
if ((ret = check_choice(xp, ytchoice, ytcase, xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
#else
x = NULL; /* Find a child with same yang spec */
while ((x = xml_child_each(xp, x, CX_ELMNT)) != NULL) {
if (x == xt)
continue;
y = xml_spec(x);
if (y == yt) /* eg same list */
continue;
yp = yang_parent_get(y);
switch (yang_keyword_get(yp)){
case Y_CASE:
if (yang_parent_get(yp) != ytchoice) /* Not same choice (not relevant) */
continue;
if (yp == ytcase) /* same choice but different case */
continue;
break;
case Y_CHOICE:
if (yp != ytchoice) /* Not same choice (not relevant) */
continue;
break;
default:
continue; /* not choice */
break;
}
if (xret && netconf_bad_element_xml(xret, "application", xml_name(x), "Element in choice statement already exists") < 0)
goto done;
goto fail;
} /* while */
#endif
ok:
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Check that an XML tree of type list has valid keys
*
* @param[in] xt XML tree
* @param[in] yt Yang spec of xt of type LIST which is a config true node.
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
* This ensures that there are keys in XML corresponding to the Yang
* This function is broken out from generic validation since it needs specially checked in incoming
* edit-config
* XXX: It does not check double (more than one) key
* @see xml_yang_validate_list_key_only
*/
static int
check_list_key(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
yang_stmt *yc;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
if (yt == NULL || !yang_config(yt) || yang_keyword_get(yt) != Y_LIST){
clixon_err(OE_YANG, EINVAL, "yt is not a config true list node");
goto done;
}
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
if (yang_keyword_get(yc) != Y_KEY)
continue;
/* Check if a list does not have mandatory key leafs */
cvk = yang_cvec_get(yt); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (xml_find_type(xt, NULL, keyname, CX_ELMNT) == NULL){
if (xret){
cbuf *cb = NULL;
yang_stmt *ymod;
enum rfc_6020 keyw;
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
ymod = ys_module(yt);
keyw = yang_keyword_get(yt);
cprintf(cb, "Mandatory key in '%s %s' in %s.yang:%d",
yang_key2str(keyw), xml_name(xt), yang_argument_get(ymod),
yang_linenum_get(yc));
if (netconf_missing_element_xml(xret, "application", keyname, cbuf_get(cb)) < 0)
goto done;
cbuf_free(cb);
}
goto fail;
}
}
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Go through all case:s children, ensure all mandatory nodes are marked, else error. Then clear
*
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
*/
static int
choice_mandatory_check(cxobj *xt,
yang_stmt *ycase,
cxobj **xret)
{
int retval = -1;
yang_stmt *yc = NULL;
cbuf *cb = NULL;
int fail = 0;
int ret;
while ((yc = yn_each(ycase, yc)) != NULL) {
if ((ret = yang_xml_mandatory(xt, yc)) < 0)
goto done;
if (ret == 1){
if (yang_flag_get(yc, YANG_FLAG_MARK))
yang_flag_reset(yc, YANG_FLAG_MARK);
else if (fail == 0){
fail++;
if (xret){
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Mandatory variable %s in module %s",
yang_argument_get(yc),
yang_argument_get(ys_module(ycase)));
if (netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0)
goto done;
}
}
}
}
if (fail)
retval = 0;
else
retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Find yang node which is ancestor of ys (or ys itself) and child of ytop
*
* Assume tree: ytop ... ys
* Return: ytop --- ymp --- ym ... ys
* @retval: 0 No match
* @retval: 1 Match, ym, ymp set
* Maybe move to clixon_yang.c ?
*/
static int
yang_ancestor_child(yang_stmt *ys,
yang_stmt *ytop,
yang_stmt **ym,
yang_stmt **ymp)
{
yang_stmt *y;
yang_stmt *yprev = NULL;
yang_stmt *yp;
y = ys;
while (y != NULL){
yp = yang_parent_get(y);
if (yp != NULL && yp == ytop){
*ym = yprev;
*ymp = y;
return 1;
}
yprev = y;
y = yp;
}
*ym = NULL;
*ymp = NULL;
return 0;
}
/*! Check mandatory nodes in current case
*
* RFC7950 7.9.4 states:
* if this ancestor is a case node, the constraint is
* enforced if any other node from the case exists.
* Algorithm uses a yang mark flag to detect if mandatory xml nodes exist:
* A priori:
* - xt is any XML node
* - xt has a choice child with YANG node yc (The xt child is not needed)
* Algoritm:
* - For each x child of xt with yang spec y:
* 1. if y has an ancestor ym with a CASE parent and grand-parent yc, eg:
* yc(CHOICE) --- ycnew(CASE) --- ym ... y
* then mark ym
* 2. If any child of ycnew has any unmarked mandatory child, then failure
* @note Only works for first case in hierarchy, does not work for mandatory
* nodes in a deeper choicce hierarchy (choice in choice).
* @param[in] xt XML node to be validated
* @param[in] yc Yang choice
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
*/
static int
check_mandatory_case(cxobj *xt,
yang_stmt *yc,
cxobj **xret)
{
int retval = 0;
cxobj *x;
yang_stmt *y;
yang_stmt *ym;
yang_stmt *ycnew;
yang_stmt *ycase;
int ret;
ycase = NULL;
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL &&
yang_ancestor_child(y, yc, &ym, &ycnew) != 0 &&
yang_keyword_get(ycnew) == Y_CASE){
if (ym){
if ((ret = yang_xml_mandatory(xt, ym)) < 0)
goto done;
if (ret == 1){
if (yang_flag_get(ym, YANG_FLAG_MARK) != 0){
clixon_debug(CLIXON_DBG_DEFAULT, "%s Already marked, shouldnt happen", __FUNCTION__);
}
yang_flag_set(ym, YANG_FLAG_MARK);
}
}
if (ycase != NULL){
if (ycnew != ycase){ /* End of case, new case */
/* Check and clear marked mandatory */
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
ycase = ycnew;
}
}
else /* New case */
ycase = ycnew;
}
else if (ycase != NULL){ /* End of case */
/* Check and clear marked mandatory */
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
ycase = NULL;
}
}
if (ycase){
if ((ret = choice_mandatory_check(xt, ycase, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Check if an xml node lacks mandatory children
*
* @param[in] xt XML node to be validated
* @param[in] yt xt:s yang statement
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
*/
static int
check_mandatory(cxobj *xt,
yang_stmt *yt,
cxobj **xret)
{
int retval = -1;
cxobj *x;
yang_stmt *y;
yang_stmt *yc;
yang_stmt *yp;
cbuf *cb = NULL;
int ret;
if (yt == NULL || !yang_config(yt)){
clixon_err(OE_YANG, EINVAL, "yt is not config true");
goto done;
}
if (yang_keyword_get(yt) == Y_LIST){
if ((ret = check_list_key(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
/* Choice is more complex because of choice/case structure and possibly hierarchical */
if (yang_keyword_get(yc) == Y_CHOICE){
if (yang_xml_mandatory(xt, yc)){
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL &&
(yp = yang_choice(y)) != NULL &&
yp == yc){
break; /* leave loop with x set */
}
}
if (x == NULL){
/* @see RFC7950: 15.6 Error Message for Data That Violates
* a Mandatory "choice" Statement */
if (xret && netconf_missing_choice_xml(xret, xt, yang_argument_get(yc), NULL) < 0)
goto done;
goto fail;
}
}
/* Check mandatory nodes in case according to RFC7950 7.9.4 */
if ((ret = check_mandatory_case(xt, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if ((ret = yang_xml_mandatory(xt, yc)) < 0) /* Rest of yangs are immediate children */
goto done;
if (ret == 0)
continue;
switch (yang_keyword_get(yc)){
case Y_CONTAINER:
case Y_ANYDATA:
case Y_ANYXML:
case Y_LEAF:
if (yang_config(yc)==0)
break;
/* Find a child with the mandatory yang */
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((y = xml_spec(x)) != NULL
&& y==yc)
break; /* got it */
}
if (x == NULL){
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Mandatory variable of %s in module %s", xml_name(xt), yang_argument_get(ys_module(yc)));
if (xret && netconf_missing_element_xml(xret, "application", yang_argument_get(yc), cbuf_get(cb)) < 0)
goto done;
goto fail;
}
break;
default:
break;
} /* switch */
}
retval = 1;
done:
if (cb)
cbuf_free(cb);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification for added entry
*
* 1. Check if mandatory leafs present as subs.
* 2. Check leaf values, eg int ranges and string regexps.
* @param[in] xt XML node to be validated
* @param[out] xret Error XML tree, as rpc-reply/rpc-error. Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *xret = NULL;
* if ((ret = xml_yang_validate_add(h, x, &xret)) < 0)
* err;
* if (ret == 0)
* fail;
* @endcode
* @see xml_yang_validate_all
* @see xml_yang_validate_rpc
* @note Should need a variant accepting cxobj **xret
*/
int
xml_yang_validate_add(clixon_handle h,
cxobj *xt,
cxobj **xret)
{
int retval = -1;
cg_var *cv = NULL;
char *reason = NULL;
yang_stmt *yt; /* yang spec of xt going in */
char *body;
int ret;
cxobj *x;
cg_var *cv0;
enum cv_type cvtype;
validate_level vl = VL_NONE;
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
if ((ret = xml_yang_mount_get(h, xt, &vl, NULL)) < 0)
goto done;
/* Check if validate beyond mountpoints */
if (ret == 1 && vl == VL_NONE)
goto ok;
}
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((yt = xml_spec(xt)) != NULL && yang_config(yt) != 0){
if ((ret = check_choice_child(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Check leaf values */
switch (yang_keyword_get(yt)){
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
/* validate value against ranges, etc */
if ((cv0 = yang_cv_get(yt)) == NULL)
break;
if ((cv = cv_dup(cv0)) == NULL){
clixon_err(OE_UNIX, errno, "cv_dup");
goto done;
}
/* In the union and leafref case, value is parsed as generic REST type,
* needs to be reparsed when concrete type is selected
* see ys_cv_validate_union_one and ys_cv_validate_leafref
*/
if ((body = xml_body(xt)) == NULL){
/* We do not allow ints to be empty. Otherwise NULL strings
* are considered as "" */
cvtype = cv_type_get(cv);
if (cv_isint(cvtype) || cvtype == CGV_BOOL || cvtype == CGV_DEC64){
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), "Invalid NULL value") < 0)
goto done;
goto fail;
}
}
else{
if (cv_parse1(body, cv, &reason) != 1){
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
goto done;
goto fail;
}
}
if ((ret = ys_cv_validate(h, cv, yt, NULL, &reason)) < 0)
goto done;
if (ret == 0){
if (xret && netconf_bad_element_xml(xret, "application", yang_argument_get(yt), reason) < 0)
goto done;
goto fail;
}
break;
default:
break;
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_add(h, x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
if (cv)
cv_free(cv);
if (reason)
free(reason);
return retval;
fail:
retval = 0;
goto done;
}
/*! Some checks done only at edit_config, eg keys in lists
*
* @param[in] xt XML tree
* @param[out] xret Error XML tree. Free with xml_free after use
* @retval 0 OK
* @retval -1 Error
*/
int
xml_yang_validate_list_key_only(cxobj *xt,
cxobj **xret)
{
int retval = -1;
yang_stmt *yt; /* yang spec of xt going in */
int ret;
cxobj *x;
/* if not given by argument (override) use default link
and !Node has a config sub-statement and it is false */
if ((yt = xml_spec(xt)) != NULL &&
yang_config(yt) != 0 &&
yang_keyword_get(yt) == Y_LIST){
if ((ret = check_list_key(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_list_key_only(x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
static int
xml_yang_validate_leaf_union(clixon_handle h,
cxobj *xt,
yang_stmt *yt,
yang_stmt *yrestype,
cxobj **xret)
{
int retval = -1;
int ret;
yang_stmt *ytsub = NULL;
cxobj *xret1 = NULL;
yang_stmt *ytype; /* resolved type */
char *restype;
/* Enough that one is valid, eg returns 1,otherwise fail */
while ((ytsub = yn_each(yrestype, ytsub)) != NULL){
if (yang_keyword_get(ytsub) != Y_TYPE)
continue;
/* Resolve the sub-union type to a resolved type */
if (yang_type_resolve(yt, yt, ytsub, /* in */
&ytype, NULL, /* resolved type */
NULL, NULL, NULL, NULL) < 0)
goto done;
restype = ytype?yang_argument_get(ytype):NULL;
ret = 1; /* If not leafref/identityref it is valid on this level */
if (strcmp(restype, "leafref") == 0){
if ((ret = validate_leafref(xt, yt, ytype, &xret1)) < 0) // XXX
goto done;
}
else if (strcmp(restype, "identityref") == 0){
if ((ret = validate_identityref(xt, yt, ytype, &xret1)) < 0)
goto done;
}
else if (strcmp("union", yang_argument_get(ytsub)) == 0){
if ((ret = xml_yang_validate_leaf_union(h, xt, yt, ytype, &xret1)) < 0)
goto done;
}
if (ret == 1)
break;
if (ret == 0 && xret1 != NULL) {
/* If validation failed, save reason, reset error and continue,
* save latest reason if nothing validates.
*/
if (*xret)
xml_free(*xret);
*xret = xret1;
xret1 = NULL;
}
}
if (ytsub == NULL)
goto fail;
retval = 1;
done:
if (xret1)
xml_free(xret1);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification for all (not only added) entries
*
* 1. Check leafrefs. Eg you delete a leaf and a leafref references it.
* @param[in] xt XML node to be validated
* @param[out] xret Error XML tree (if retval=0). Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (cbret set)
* @retval -1 Error
* @code
* cxobj *x;
* cbuf *xret = NULL;
* if ((ret = xml_yang_validate_all(h, x, &xret)) < 0)
* err;
* if (ret == 0)
* fail;
* xml_free(xret);
* @endcode
* @see xml_yang_validate_add
* @see xml_yang_validate_rpc
*/
int
xml_yang_validate_all(clixon_handle h,
cxobj *xt,
cxobj **xret)
{
int retval = -1;
yang_stmt *yt; /* yang node associated with xt */
yang_stmt *yc; /* yang child */
yang_stmt *ye; /* yang must error-message */
char *xpath;
int nr;
int ret;
cxobj *x;
cxobj *xp;
char *ns = NULL;
cbuf *cb = NULL;
cvec *nsc = NULL;
int hit = 0;
validate_level vl = VL_NONE;
if (clicon_option_bool(h, "CLICON_YANG_SCHEMA_MOUNT")){
if ((ret = xml_yang_mount_get(h, xt, &vl, NULL)) < 0)
goto done;
/* Check if validate beyond mountpoints */
if (ret == 1 && vl == VL_NONE)
goto ok;
}
/* if not given by argument (overide) use default link
and !Node has a config sub-statement and it is false */
if ((yt = xml_spec(xt)) == NULL){
if (clicon_option_bool(h, "CLICON_YANG_UNKNOWN_ANYDATA") == 1) {
clixon_log(h, LOG_WARNING,
"%s: %d: No YANG spec for %s, validation skipped",
__FUNCTION__, __LINE__, xml_name(xt));
goto ok;
}
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed to find YANG spec of XML node: %s", xml_name(xt));
if ((xp = xml_parent(xt)) != NULL)
cprintf(cb, " with parent: %s", xml_name(xp));
if (xml2ns(xt, xml_prefix(xt), &ns) < 0)
goto done;
if (ns)
cprintf(cb, " in namespace: %s", ns);
if (xret && netconf_unknown_element_xml(xret, "application", xml_name(xt), cbuf_get(cb)) < 0)
goto done;
goto fail;
}
if (yang_config(yt) != 0){
if (yang_check_when_xpath(xt, xml_parent(xt), yt, &hit, &nr, &xpath) < 0)
goto done;
if (hit && nr == 0){
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed WHEN condition of %s in module %s (WHEN xpath is %s)",
xml_name(xt),
yang_argument_get(ys_module(yt)),
xpath);
if (xret && netconf_operation_failed_xml(xret, "application",
cbuf_get(cb)) < 0)
goto done;
goto fail;
}
if ((ret = check_mandatory(xt, yt, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Node-specific validation */
switch (yang_keyword_get(yt)){
case Y_ANYXML:
case Y_ANYDATA:
goto ok;
break;
case Y_LEAF:
/* fall thru */
case Y_LEAF_LIST:
/* Special case if leaf is leafref, then first check against
current xml tree
*/
/* Get base type yc */
if (yang_type_get(yt, NULL, &yc, NULL, NULL, NULL, NULL, NULL) < 0)
goto done;
if (strcmp(yang_argument_get(yc), "leafref") == 0){
if ((ret = validate_leafref(xt, yt, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
else if (strcmp(yang_argument_get(yc), "identityref") == 0){
if ((ret = validate_identityref(xt, yt, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
else if (strcmp("union", yang_argument_get(yc)) == 0){
if ((ret = xml_yang_validate_leaf_union(h, xt, yt, yc, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
break;
default:
break;
}
/* must sub-node RFC 7950 Sec 7.5.3. Can be several.
* XXX. use yang path instead? */
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
if (yang_keyword_get(yc) != Y_MUST)
continue;
xpath = yang_argument_get(yc); /* "must" has xpath argument */
/* the context node is the node in the accessible tree for
* which the "must" statement is defined.
* The set of namespace declarations is the set of all "import" statements'
*/
if (xml_nsctx_yang(yc, &nsc) < 0)
goto done;
if ((nr = xpath_vec_bool(xt, nsc, "%s", xpath)) < 0)
goto done;
if (!nr){
ye = yang_find(yc, Y_ERROR_MESSAGE, NULL);
if ((cb = cbuf_new()) == NULL){
clixon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "Failed MUST xpath '%s' of '%s' in module %s",
xpath, xml_name(xt), yang_argument_get(ys_module(yt)));
if (xret && netconf_operation_failed_xml(xret, "application",
ye?yang_argument_get(ye):cbuf_get(cb)) < 0)
goto done;
goto fail;
}
if (nsc){
xml_nsctx_free(nsc);
nsc = NULL;
}
}
}
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
/* Check unique and min-max after choice test for example*/
if (yang_config(yt) != 0){
/* Checks if next level contains any unique list constraints */
if ((ret = xml_yang_minmax_recurse(xt, 1, xret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
ok:
retval = 1;
done:
if (cb)
cbuf_free(cb);
if (nsc)
xml_nsctx_free(nsc);
return retval;
fail:
retval = 0;
goto done;
}
/*! Validate a single XML node with yang specification
*
* @param[in] h Clixon handle
* @param[out] xret Error XML tree (if ret == 0). Free with xml_free after use
* @retval 1 Validation OK
* @retval 0 Validation failed (xret set)
* @retval -1 Error
*/
int
xml_yang_validate_all_top(clixon_handle h,
cxobj *xt,
cxobj **xret)
{
int ret;
cxobj *x;
x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
if ((ret = xml_yang_validate_all(h, x, xret)) < 1)
return ret;
}
if ((ret = xml_yang_minmax_recurse(xt, 0, xret)) < 1)
return ret;
return 1;
}
/*! Check validity of outgoing RPC
*
* Rewrite return message if errors
* @param[in,out] cbret
* @retval 0 OK
* @retval -1 Error
* @note Parses cbret which seems one time too many
*/
int
rpc_reply_check(clixon_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){
clixon_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(h, x, rpcname, yspec, &xret)) < 0)
goto done;
if (ret == 0){
clixon_debug(CLIXON_DBG_DEFAULT, "%s failure when validating:%s", __FUNCTION__, cbuf_get(cbret));
cbuf_reset(cbret);
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 0)
goto done;
goto fail;
}
if ((ret = xml_yang_validate_rpc_reply(h, x, &xret)) < 0)
goto done;
if (ret == 0){
clixon_debug(CLIXON_DBG_DEFAULT, "%s failure when validating:%s", __FUNCTION__, cbuf_get(cbret));
cbuf_reset(cbret);
if (clixon_xml2cbuf(cbret, xret, 0, 0, NULL, -1, 0) < 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;
}