diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index b055f831..44a26f2e 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -424,19 +424,28 @@ api_data_post(clicon_handle h, /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* 4.4.1: The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -628,19 +637,28 @@ api_data_put(clicon_handle h, /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } x = xml_child_i(xdata,0); @@ -662,13 +680,19 @@ api_data_put(clicon_handle h, else { /* Check same symbol in api-path as data */ if (strcmp(xml_name(x), xml_name(xbot))){ - badrequest(r); + if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* If list or leaf-list, api-path keys must match data keys */ if (y && (y->yn_keyword == Y_LIST ||y->yn_keyword == Y_LEAF_LIST)){ if (match_list_keys((yang_stmt*)y, x, xbot) < 0){ - badrequest(r); + if (netconf_operation_failed_xml(&xerr, "protocol", "api-path keys do not match data keys") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } @@ -1055,12 +1079,18 @@ api_operations_post(clicon_handle h, /* Parse input data as json or xml into xml */ if (parse_xml){ if (xml_parse_string(data, NULL, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } } else if (json_parse_str(data, &xdata) < 0){ - badrequest(r); + if (netconf_malformed_message_xml(&xerr, clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL); diff --git a/doc/Doxyfile b/doc/Doxyfile index 98375856..8dd946de 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -743,7 +743,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl ../datastore +INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl ../datastore ../datastore/text # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/doc/Doxyfile.graphs b/doc/Doxyfile.graphs index 03ca9505..7eaf3ef0 100644 --- a/doc/Doxyfile.graphs +++ b/doc/Doxyfile.graphs @@ -743,7 +743,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore +INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl ../datastore ../datastore/text # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses diff --git a/example/README.md b/example/README.md index eed8f40c..86e70769 100644 --- a/example/README.md +++ b/example/README.md @@ -93,27 +93,22 @@ Routing notification The example includes a restonf, netconf, CLI and two backend plugins. Each plugin is initiated with an API struct followed by a plugin init function. -The content of the API struct is different depending on what kind of plugin it is. Some fields are -meaningful only for some plugins. -The plugin init function may also include registering RPC functions. +The content of the API struct is different depending on what kind of plugin it is. +The plugin init function may also include registering RPC functions, see below is for a backend. ``` static clixon_plugin_api api = { "example", /* name */ clixon_plugin_init, plugin_start, plugin_exit, - NULL, /* cli prompt N/A for backend */ - NULL, /* cli suspend N/A for backend */ - NULL, /* cli interrupt N/A for backend */ - NULL, /* auth N/A for backend */ - plugin_reset, - plugin_statedata, - transaction_begin, - transaction_validate, - transaction_complete, - transaction_commit, - transaction_end, - transaction_abort + .ca_reset=plugin_reset,/* reset */ + .ca_statedata=plugin_statedata, /* statedata */ + .ca_trans_begin=NULL, /* trans begin */ + .ca_trans_validate=transaction_validate,/* trans validate */ + .ca_trans_complete=NULL, /* trans complete */ + .ca_trans_commit=transaction_commit, /* trans commit */ + .ca_trans_end=NULL, /* trans end */ + .ca_trans_abort=NULL /* trans abort */ }; clixon_plugin_api * diff --git a/example/example_backend.c b/example/example_backend.c index 696f8660..495ad14f 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -260,13 +260,13 @@ plugin_start(clicon_handle h, clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { - "example", /* name */ /*--- Common fields. ---*/ - clixon_plugin_init, /* init */ - plugin_start, /* start */ - NULL, /* exit */ - .ca_reset=plugin_reset,/* reset */ /*--- Backend plugin only ---*/ - .ca_statedata=plugin_statedata, /* statedata */ - .ca_trans_begin=NULL, /* trans begin */ + "example", /* name */ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + NULL, /* exit */ + .ca_reset=plugin_reset, /* reset */ + .ca_statedata=plugin_statedata, /* statedata */ + .ca_trans_begin=NULL, /* trans begin */ .ca_trans_validate=transaction_validate,/* trans validate */ .ca_trans_complete=NULL, /* trans complete */ .ca_trans_commit=transaction_commit, /* trans commit */ diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 8ba54c1c..f65bc9dc 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -61,5 +61,6 @@ int netconf_operation_not_supported(cbuf *cb, char *type, char *message); int netconf_operation_failed(cbuf *cb, char *type, char *message); int netconf_operation_failed_xml(cxobj **xret, char *type, char *message); int netconf_malformed_message(cbuf *cb, char *message); +int netconf_malformed_message_xml(cxobj **xret, char *message); #endif /* _CLIXON_NETCONF_LIB_H */ diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index a1efe18a..f6854d15 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -49,10 +49,16 @@ */ /*! Controls how keywords a generated in CLI syntax / prints from object model - * Example syntax a.b[] $!x $y: - * NONE: a b ; - * VARS: a b y ; - * ALL: a b x y ; + * Example YANG: + * list a {a.b[] $!x $y: + * list a { + * key x; + * leaf x; + * leaf y; + * } + * NONE: a ; + * VARS: a y ; + * ALL: a x y ; */ enum genmodel_type{ GT_ERR =-1, /* Error */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 589bfb04..7c4d4b21 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -31,6 +31,9 @@ ***** END LICENSE BLOCK ***** + * Yang functions + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ #ifndef _CLIXON_YANG_H_ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 866efec6..a7cf2f9e 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -526,22 +526,25 @@ netconf_access_denied_xml(cxobj **xret, char *message) { int retval =-1; - cbuf *cbret = NULL; - - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; } - if (netconf_access_denied(cbret, type, message) < 0) + else if (xml_name_set(*xret, "rpc-reply") < 0) goto done; - if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_rootchild(*xret, 0, xret) < 0) + if (xml_parse_va(&xerr, NULL, "access-denied" + "%s" + "error", type) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) goto done; retval = 0; done: - if (cbret) - cbuf_free(cbret); return retval; } @@ -835,22 +838,25 @@ netconf_operation_failed_xml(cxobj **xret, char *message) { int retval =-1; - cbuf *cbret = NULL; - - if ((cbret = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; } - if (netconf_operation_failed(cbret, type, message) < 0) + else if (xml_name_set(*xret, "rpc-reply") < 0) goto done; - if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) goto done; - if (xml_rootchild(*xret, 0, xret) < 0) + if (xml_parse_va(&xerr, NULL, "operation-failed" + "%s" + "error", type) < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) goto done; retval = 0; done: - if (cbret) - cbuf_free(cbret); return retval; } @@ -892,3 +898,39 @@ netconf_malformed_message(cbuf *cb, clicon_err(OE_XML, errno, "cprintf"); goto done; } + +/*! Create Netconf malformed-message error XML tree according to RFC 6241 App A + * + * A message could not be handled because it failed to be parsed correctly. + * For example, the message is not well-formed XML or it uses an + * invalid character set. + * @param[out] xret Error XML tree + * @param[in] message Error message + * @note New in :base:1.1 + */ +int +netconf_malformed_message_xml(cxobj **xret, + char *message) +{ + int retval =-1; + cxobj *xerr; + + if (*xret == NULL){ + if ((*xret = xml_new("rpc-reply", NULL, NULL)) == NULL) + goto done; + } + else if (xml_name_set(*xret, "rpc-reply") < 0) + goto done; + if ((xerr = xml_new("rpc-error", *xret, NULL)) == NULL) + goto done; + if (xml_parse_va(&xerr, NULL, "malformed-message" + "rpc" + "error") < 0) + goto done; + if (message && xml_parse_va(&xerr, NULL, "%s", + message) < 0) + goto done; + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_yang_parse.h b/lib/src/clixon_yang_parse.h index 99ce0b5d..0b9b1ec4 100644 --- a/lib/src/clixon_yang_parse.h +++ b/lib/src/clixon_yang_parse.h @@ -34,8 +34,9 @@ ***** END LICENSE BLOCK ***** - * Database specification parser cli syntax - * (Cloned from cligen parser) + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ #ifndef _CLIXON_YANG_PARSE_H_ #define _CLIXON_YANG_PARSE_H_ diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index 8c80af65..8c65cbd1 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -1,8 +1,4 @@ /* - * Yang 1.0 parser according to RFC6020. - * It is hopefully useful but not complete - * RFC7950 defines Yang version 1.1 - * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren @@ -34,8 +30,9 @@ ***** END LICENSE BLOCK ***** - * Database specification parser cli syntax - * (Cloned from cligen parser) + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ %{ diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index b27f63ea..7cf0d253 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -1,7 +1,4 @@ /* - * Yang 1.0 parser according to RFC6020. - * It is hopefully useful but not complete - * RFC7950 defines Yang version 1.1 * ***** BEGIN LICENSE BLOCK ***** @@ -34,6 +31,9 @@ ***** END LICENSE BLOCK ***** + * Yang parser. Hopefully useful but not complete + * @see https://tools.ietf.org/html/rfc6020 YANG 1.0 + * @see https://tools.ietf.org/html/rfc7950 YANG 1.1 */ diff --git a/test/test_restconf.sh b/test/test_restconf.sh index bee9866f..344b521f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -137,7 +137,7 @@ expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new2 "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '' +expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" "" new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} @@ -213,11 +213,11 @@ expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": new2 "restconf Re-post eth/0/0 which should generate error" expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' -new2 "Add leaf description using POST" +new "Add leaf description using POST" expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Add nothing using POST" -expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "data is in some way badly formed" +expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "malformed-message","error-type": "rpc","error-severity": "error","error-message": " on line 1: syntax error at or before:' new2 "restconf Check description added" expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} @@ -232,7 +232,7 @@ expectfn 'curl -s -G http://localhost/restconf/data' $state new2 "restconf Re-Delete eth/0/0 using none should generate error" expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} ' -new2 "restconf Add subtree eth/0/0 using PUT" +new "restconf Add subtree eth/0/0 using PUT" expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new2 "restconf get subtree" @@ -243,9 +243,12 @@ new2 "restconf rpc using POST json" expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}} ' +# Cant get this to work due to quoting +#new2 "restconf rpc using POST wrong JSON" +#expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":ipv4}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": " on line 1: syntax error at or before: i"}}}} ' + new2 "restconf rpc using POST json w/o mandatory element" expecteq "$(curl -s -X POST -d '{"input":{"wrongelement":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Missing mandatory variable: routing-instance-name"}}}} ' - new2 "restconf rpc non-existing rpc w/o namespace" expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 928defa4..df401e24 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -134,7 +134,7 @@ new "restconf PUT add interface" expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' "" new "restconf PUT change key error" -expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' "Bad request" +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "api-path keys do not match data keys"}}}}' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf