From 36d310de8844066423f07b3f472703a75b11a175 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 12 Feb 2018 09:18:52 +0700 Subject: [PATCH 01/50] developbranch 3.6.0.PRE --- CHANGELOG.md | 2 ++ configure | 4 ++-- configure.ac | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88e37821..8e472d79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Clixon Changelog +## 3.6.0 (Upcoming) + ## 3.5.0 (12 February 2018) ### Major changes: diff --git a/configure b/configure index 6701990d..44813e8a 100755 --- a/configure +++ b/configure @@ -2150,9 +2150,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu : ${CFLAGS="-O2"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="5" +CLIXON_VERSION_MINOR="6" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then diff --git a/configure.ac b/configure.ac index 7b396216..b0bbed2f 100644 --- a/configure.ac +++ b/configure.ac @@ -42,9 +42,9 @@ AC_INIT(lib/clixon/clixon.h.in) : ${CFLAGS="-O2"} CLIXON_VERSION_MAJOR="3" -CLIXON_VERSION_MINOR="5" +CLIXON_VERSION_MINOR="6" CLIXON_VERSION_PATCH="0" -CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" +CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}.PRE\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" if test "$prefix" = "NONE"; then From 278f3de840fe0277425ae74cf02dc5dc98ace3d6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 13 Feb 2018 09:17:32 +0700 Subject: [PATCH 02/50] Adding username to all restconf backend calls --- apps/cli/cli_common.c | 8 +- apps/cli/cli_show.c | 6 +- apps/restconf/restconf_main.c | 22 +++--- apps/restconf/restconf_methods.c | 126 ++++++++++++++++++------------- apps/restconf/restconf_methods.h | 17 +++-- lib/clixon/clixon_proto_client.h | 4 +- lib/src/clixon_proto_client.c | 14 +++- 7 files changed, 115 insertions(+), 82 deletions(-) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index c0266ab6..a3d40b51 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -654,13 +654,13 @@ compare_dbs(clicon_handle h, astext = cv_int32_get(cvec_i(argv, 0)); else astext = 0; - if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0) + if (clicon_rpc_get_config(h, "running", "/", NULL, &xc1) < 0) goto done; if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0) + if (clicon_rpc_get_config(h, "candidate", "/", NULL, &xc2) < 0) goto done; if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -827,7 +827,7 @@ save_config_file(clicon_handle h, goto done; } filename = cv_string_get(cv); - if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) + if (clicon_rpc_get_config(h, dbstr,"/", NULL, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -1171,7 +1171,7 @@ cli_copy_config(clicon_handle h, cprintf(cb, xpath, keyname, fromname); /* Get from object configuration and store in x1 */ - if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0) + if (clicon_rpc_get_config(h, db, cbuf_get(cb), NULL, &x1) < 0) goto done; if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 0c3769d7..85645413 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -156,7 +156,7 @@ expand_dbvar(void *h, goto done; /* XXX read whole configuration, why not send xpath? */ - if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0) + if (clicon_rpc_get_config(h, dbstr, "/", NULL, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -484,7 +484,7 @@ cli_show_config(clicon_handle h, else cprintf(cbxpath, "%s", xpath); /* Get configuration from database */ - if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0) + if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), NULL, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -568,7 +568,7 @@ show_conf_xpath(clicon_handle h, } cv = cvec_find_var(cvv, "xpath"); xpath = cv_string_get(cv); - if (clicon_rpc_get_config(h, str, xpath, &xt) < 0) + if (clicon_rpc_get_config(h, str, xpath, NULL, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 54b422ed..5ae99543 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -89,7 +89,8 @@ * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] dvec Stream input data + * @param[in] dvec Stream input daat + * @param[in] username Authenticated user */ static int api_data(clicon_handle h, @@ -98,7 +99,8 @@ api_data(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + char *username) { int retval = -1; char *request_method; @@ -109,17 +111,17 @@ api_data(clicon_handle h, if (strcmp(request_method, "OPTIONS")==0) retval = api_data_options(h, r); else if (strcmp(request_method, "HEAD")==0) - retval = api_data_head(h, r, pcvec, pi, qvec); + retval = api_data_head(h, r, pcvec, pi, qvec, username); else if (strcmp(request_method, "GET")==0) - retval = api_data_get(h, r, pcvec, pi, qvec); + retval = api_data_get(h, r, pcvec, pi, qvec, username); else if (strcmp(request_method, "POST")==0) - retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data); + retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "PUT")==0) - retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data); + retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "PATCH")==0) - retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data); + retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "DELETE")==0) - retval = api_data_delete(h, r, api_path, pi); + retval = api_data_delete(h, r, api_path, pi, username); else retval = notfound(r); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -152,7 +154,7 @@ api_operations(clicon_handle h, request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); if (strcmp(request_method, "GET")==0) - retval = api_operation_get(h, r, path, pcvec, pi, qvec, data); + retval = api_operation_get(h, r, path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "POST")==0) retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username); else @@ -335,7 +337,7 @@ api_restconf(clicon_handle h, goto done; } else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, r, path, pcvec, 2, qvec, data) < 0) + if (api_data(h, r, path, pcvec, 2, qvec, data, username) < 0) goto done; } else if (strcmp(method, "operations") == 0){ /* rpc */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 69bae763..992a8662 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -218,6 +218,7 @@ api_data_get2(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, + char *username, int head) { int retval = -1; @@ -252,7 +253,7 @@ api_data_get2(clicon_handle h, } path = cbuf_get(cbpath); clicon_debug(1, "%s path:%s", __FUNCTION__, path); - if (clicon_rpc_get(h, path, &xret) < 0){ + if (clicon_rpc_get(h, path, username, &xret) < 0){ notfound(r); goto ok; } @@ -338,12 +339,13 @@ api_data_get2(clicon_handle h, */ int api_data_head(clicon_handle h, - FCGX_Request *r, - cvec *pcvec, - int pi, - cvec *qvec) + FCGX_Request *r, + cvec *pcvec, + int pi, + cvec *qvec, + char *username) { - return api_data_get2(h, r, pcvec, pi, qvec, 1); + return api_data_get2(h, r, pcvec, pi, qvec, username, 1); } /*! REST GET method @@ -374,9 +376,10 @@ api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec) + cvec *qvec, + char *username) { - return api_data_get2(h, r, pcvec, pi, qvec, 0); + return api_data_get2(h, r, pcvec, pi, qvec, username, 0); } /*! Generic REST POST method @@ -412,7 +415,8 @@ api_data_post(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + char *username) { int retval = -1; enum operation_type op = OP_CREATE; @@ -425,6 +429,7 @@ api_data_post(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; + cxobj *xu; char *media_content_type; int parse_xml = 0; /* By default expect and parse JSON */ @@ -446,6 +451,16 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; + /* For internal XML protocol: add username attribute for backend access control + */ + if (username){ + if ((xu = xml_new("username", xtop, NULL)) == NULL) + goto done; + xml_type_set(xu, CX_ATTR); + if (xml_value_set(xu, username) < 0) + goto done; + } + if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ @@ -589,7 +604,8 @@ api_data_put(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + char *username) { int retval = -1; enum operation_type op = OP_REPLACE; @@ -603,6 +619,7 @@ api_data_put(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; + cxobj *xu; char *media_content_type; int parse_xml = 0; /* By default expect and parse JSON */ char *api_path; @@ -625,6 +642,15 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; + /* For internal XML protocol: add username attribute for backend access control + */ + if (username){ + if ((xu = xml_new("username", xtop, NULL)) == NULL) + goto done; + xml_type_set(xu, CX_ATTR); + if (xml_value_set(xu, username) < 0) + goto done; + } if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ @@ -727,12 +753,13 @@ api_data_put(clicon_handle h, */ int api_data_patch(clicon_handle h, - FCGX_Request *r, - char *api_path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data) + FCGX_Request *r, + char *api_path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + char *username) { notimplemented(r); return 0; @@ -751,13 +778,15 @@ int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, - int pi) + int pi, + char *username) { int retval = -1; int i; cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; cxobj *xa; + cxobj *xu; cbuf *cbx = NULL; yang_node *y = NULL; yang_spec *yspec; @@ -774,6 +803,15 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; + /* For internal XML protocol: add username attribute for backend access control + */ + if (username){ + if ((xu = xml_new("username", xtop, NULL)) == NULL) + goto done; + xml_type_set(xu, CX_ATTR); + if (xml_value_set(xu, username) < 0) + goto done; + } if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) @@ -817,13 +855,15 @@ api_data_delete(clicon_handle h, */ int api_operation_get(clicon_handle h, - FCGX_Request *r, - char *path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data) + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + char *username) { + notimplemented(r); return 0; } @@ -837,8 +877,6 @@ api_operation_get(clicon_handle h, * @note We map post to edit-config create. POST {+restconf}/operations/ - - */ int api_operation_post(clicon_handle h, @@ -907,6 +945,16 @@ api_operation_post(clicon_handle h, if ((xtop = xml_new("rpc", NULL, NULL)) == NULL) goto done; xbot = xtop; + /* For internal XML protocol: add username attribute for backend access control + */ + if (username){ + if ((xa = xml_new("username", xtop, NULL)) == NULL) + goto done; + xml_type_set(xa, CX_ATTR); + if (xml_value_set(xa, username) < 0) + goto done; + } + /* XXX: something strange for rpc user */ if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) goto done; @@ -951,34 +999,6 @@ api_operation_post(clicon_handle h, } } } - /* Non-standard: add username attribute for backend ACM (RFC 6536) - * - */ - if (username){ - if ((xa = xml_new("username", xtop, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, username) < 0) - goto done; - } -#ifdef obsolete - { - cxobj *xa; - char *cookie; - char *cookieval = NULL; - - if ((cookie = FCGX_GetParam("HTTP_COOKIE", r->envp)) != NULL && - get_user_cookie(cookie, "c-user", &cookieval) ==0){ - if ((xa = xml_new("id", xtop, NULL)) == NULL) - goto done; - xml_type_set(xa, CX_ATTR); - if (xml_value_set(xa, cookieval) < 0) - goto done; - if (cookieval) - free(cookieval); - } - } -#endif /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 659a2c6c..b50b91a6 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -46,23 +46,24 @@ */ int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec); + cvec *qvec, char *username); int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec); + cvec *qvec, char *username); int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data); + cvec *qvec, char *data, char *username); int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data); + cvec *qvec, char *data, char *username); int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data); -int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi); + cvec *qvec, char *data, char *username); +int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, + char *username); int api_operation_get(clicon_handle h, FCGX_Request *r, - char *path, - cvec *pcvec, int pi, cvec *qvec, char *data); + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data, char *username); int api_operation_post(clicon_handle h, FCGX_Request *r, char *path, diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index ac917d77..7adccc68 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -45,14 +45,14 @@ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp); int clicon_rpc_generate_error(char *format, cxobj *xerr); -int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret); +int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, char *username, cxobj **xret); int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op, char *xml); int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2); int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_unlock(clicon_handle h, char *db); -int clicon_rpc_get(clicon_handle h, char *xpath, cxobj **xret); +int clicon_rpc_get(clicon_handle h, char *xpath, char *username, cxobj **xret); int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_validate(clicon_handle h, char *db); diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 4a5808cf..05991b74 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -236,6 +236,7 @@ clicon_rpc_generate_error(char *format, * @param[in] h CLICON handle * @param[in] db Name of database * @param[in] xpath XPath (or "") + * @param[in] username Authenticated user (extra attribute) * @param[out] xt XML tree. Free with xml_free. * Either or . * @retval 0 OK @@ -257,6 +258,7 @@ int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, + char *username, cxobj **xt) { int retval = -1; @@ -267,7 +269,10 @@ clicon_rpc_get_config(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); + cprintf(cb, "<%s/>", db); if (xpath && strlen(xpath)) cprintf(cb, "", xpath); cprintf(cb, ""); @@ -485,6 +490,7 @@ clicon_rpc_unlock(clicon_handle h, /*! Get database configuration and state data * @param[in] h CLICON handle * @param[in] xpath XPath (or "") + * @param[in] username Authenticated user (extra attribute) * @param[out] xt XML tree. Free with xml_free. * Either or . * @retval 0 OK @@ -505,6 +511,7 @@ clicon_rpc_unlock(clicon_handle h, int clicon_rpc_get(clicon_handle h, char *xpath, + char *username, cxobj **xt) { int retval = -1; @@ -515,7 +522,10 @@ clicon_rpc_get(clicon_handle h, if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, ""); + cprintf(cb, ""); if (xpath && strlen(xpath)) cprintf(cb, "", xpath); cprintf(cb, ""); From 2acacbf0879060ab946fee549fc2d903e2bd8803 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 15 Feb 2018 09:29:10 +0700 Subject: [PATCH 03/50] Use instead of when save/load configuration --- apps/cli/cli_common.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index a3d40b51..0a869980 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -829,10 +829,19 @@ save_config_file(clicon_handle h, filename = cv_string_get(cv); if (clicon_rpc_get_config(h, dbstr,"/", NULL, &xt) < 0) goto done; + if (xt == NULL){ + clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */ + goto done; + } if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } + /* get-config returns a tree. Save as tree so it can be used + * as data-store. + */ + if (xml_name_set(xt, "config") < 0) + goto done; if ((f = fopen(filename, "w")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); goto done; From 1e88c3887795d92b8ef491e779d3f54e2257a99f Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sat, 17 Feb 2018 20:51:42 +0000 Subject: [PATCH 04/50] translate xml->json \n correctly --- CHANGELOG.md | 4 ++++ lib/src/clixon_json.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626022fe..8d9b2b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Clixon Changelog ## 3.6.0 (Upcoming) +### Major changes: +### Minor changes: +### Corrected Bugs +* Translate xml->json \n correctly ## 3.5.0 (12 February 2018) diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 3e225396..e9ac79b5 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -216,6 +216,9 @@ json_str_escape(char *str) for (i=0;i Date: Sun, 25 Feb 2018 20:01:53 +0100 Subject: [PATCH 05/50] * Added Yang "extension" statement. This includes parsing unknown statements and identifying them as extensions or not. However, semantics for specific extensions must still be added. * Renamed ytype_id and ytype_prefix to yarg_id and yarg_prefix, respectively * Added cli_show_version() --- CHANGELOG.md | 17 ++++ apps/cli/cli_show.c | 7 ++ example/example.yang | 10 +- example/routing_cli.c | 2 +- example/routing_cli.cli | 1 + lib/clixon/clixon_yang.h | 11 ++- lib/clixon/clixon_yang_type.h | 1 - lib/src/clixon_yang.c | 83 ++++++++++++---- lib/src/clixon_yang_parse.l | 14 ++- lib/src/clixon_yang_parse.y | 137 ++++++++++++++------------ lib/src/clixon_yang_type.c | 6 +- test/test_restconf2.sh | 64 ++++++++---- test/test_yang.sh | 25 +++++ yang/ietf-netconf-acm@2012-02-22.yang | 2 +- 14 files changed, 264 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626022fe..aec816b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ ## 3.6.0 (Upcoming) +### Major changes: +### Minor changes: + +* Use instead of when save/load configuration to file. This +enables saved files to be used as datastore without any editing. Thanks Matt. + +* Added Yang "extension" statement. This includes parsing unknown + statements and identifying them as extensions or not. However, + semantics for specific extensions must still be added. + +* Renamed ytype_id and ytype_prefix to yarg_id and yarg_prefix, respectively + +* Added cli_show_version() + +### Corrected Bugs + + ## 3.5.0 (12 February 2018) ### Major changes: diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 85645413..13f58339 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -587,7 +587,14 @@ done: xml_free(xt); return retval; } + int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv) { return show_conf_xpath(h, vars, argv); } + +int cli_show_version(clicon_handle h, cvec *vars, cvec *argv) +{ + cli_output(stdout, "%s\n", CLIXON_VERSION_STRING); + return 0; +} diff --git a/example/example.yang b/example/example.yang index 3c6a6f3a..f3c39917 100644 --- a/example/example.yang +++ b/example/example.yang @@ -1,10 +1,14 @@ module example { + prefix ex; import ietf-ip { - prefix ip; + prefix ip; } import ietf-routing { - prefix rt; + prefix rt; + } + import ietf-netconf-acm { + prefix nacm; /* See RFC 6536 */ } description - "Example code that includes ietf-ip and ietf-routing"; + "Example code that includes ietf-ip and ietf-routing"; } diff --git a/example/routing_cli.c b/example/routing_cli.c index b054d943..bdb621b7 100644 --- a/example/routing_cli.c +++ b/example/routing_cli.c @@ -81,7 +81,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) /* Show eth0 interfaces config using XPATH */ if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]", - &xret) < 0) + NULL, &xret) < 0) goto done; xml_print(stdout, xret); diff --git a/example/routing_cli.cli b/example/routing_cli.cli index 3c8a6a2d..63a285eb 100644 --- a/example/routing_cli.cli +++ b/example/routing_cli.cli @@ -30,6 +30,7 @@ compare("Compare running and candidate"), compare_dbs((int32)1); show("Show a particular state of the system"){ xpath("Show configuration") ("XPATH expression"), show_conf_xpath("candidate"); + version("Show version"), cli_show_version("candidate", "text", "/"); compare("Compare candidate and running databases"), compare_dbs((int32)0);{ xml("Show comparison in xml"), compare_dbs((int32)0); text("Show comparison in text"), compare_dbs((int32)1); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 084fe3c5..c89a8f80 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -114,6 +114,7 @@ enum rfc_6020{ Y_TYPEDEF, Y_UNIQUE, Y_UNITS, + Y_UNKNOWN, Y_USES, Y_VALUE, Y_WHEN, @@ -161,7 +162,9 @@ struct yang_stmt{ leaf-list, config: boolean true or false mandatory: boolean true or false - fraction-digits for fraction-digits */ + fraction-digits for fraction-digits + unkown-stmt (argument) + */ cvec *ys_cvec; /* List of stmt-specific variables Y_RANGE: range_min, range_max Y_LIST: vector of keys @@ -206,8 +209,8 @@ yang_stmt *ys_dup(yang_stmt *old); int yn_insert(yang_node *yn_parent, yang_stmt *ys_child); yang_stmt *yn_each(yang_node *yn, yang_stmt *ys); char *yang_key2str(int keyword); -char *ytype_prefix(yang_stmt *ys); -char *ytype_id(yang_stmt *ys); +char *yarg_prefix(yang_stmt *ys); +char *yarg_id(yang_stmt *ys); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); @@ -227,7 +230,7 @@ int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, yang_stmt **yres); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); -int ys_parse_sub(yang_stmt *ys); +int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); int yang_config(yang_stmt *ys); yang_spec *yang_spec_netconf(clicon_handle h); diff --git a/lib/clixon/clixon_yang_type.h b/lib/clixon/clixon_yang_type.h index 291766cf..61b581af 100644 --- a/lib/clixon/clixon_yang_type.h +++ b/lib/clixon/clixon_yang_type.h @@ -68,7 +68,6 @@ char *cv2yang_type(enum cv_type cv_type); yang_stmt *yang_find_identity(yang_stmt *ys, char *identity); int ys_cv_validate(cg_var *cv, yang_stmt *ys, char **reason); int clicon_type2cv(char *type, char *rtype, enum cv_type *cvtype); -char *ytype_id(yang_stmt *ys); int yang_type_get(yang_stmt *ys, char **otype, yang_stmt **restype, int *options, cg_var **mincv, cg_var **maxcv, char **pattern, uint8_t *fraction_digits); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 1014b0f0..ba3758b7 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -133,7 +133,8 @@ static const map_str2int ykmap[] = { {"type", Y_TYPE}, {"typedef", Y_TYPEDEF}, {"unique", Y_UNIQUE}, - {"units", Y_UNITS}, + {"units", Y_UNITS}, + {"unknown", Y_UNKNOWN}, {"uses", Y_USES}, {"value", Y_VALUE}, {"when", Y_WHEN}, @@ -668,13 +669,15 @@ ys_spec(yang_stmt *ys) return (yang_spec*)ys; } -/* Extract id from type argument. two cases: - * argument is prefix:id, - * argument is id, +/* Assume argument is id on the type: <[prefix:]id>, return 'id' * Just return string from id + * @param[in] ys A yang statement + * @retval NULL No id (argument is NULL) + * @retval id Pointer to identifier + * @see yarg_prefix */ char* -ytype_id(yang_stmt *ys) +yarg_id(yang_stmt *ys) { char *id; @@ -685,13 +688,14 @@ ytype_id(yang_stmt *ys) return id; } -/* Extract prefix from type argument. two cases: - * argument is prefix:id, - * argument is id, - * return either NULL or a new prefix string that needs to be freed by caller. +/* Assume argument is id on the type: <[prefix:]id>, return 'prefix' + * @param[in] ys A yang statement + * @retval NULL No prefix + * @retval prefix Malloced string that needs to be freed by caller. + * @see yarg_id */ char* -ytype_prefix(yang_stmt *ys) +yarg_prefix(yang_stmt *ys) { char *id; char *prefix = NULL; @@ -822,7 +826,11 @@ yang_print_cbuf(cbuf *cb, yang_stmt *ys = NULL; while ((ys = yn_each(yn, ys)) != NULL) { - cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword)); + if (ys->ys_keyword == Y_UNKNOWN){ /* dont print unknown - proxy for extension*/ + cprintf(cb, "%*s", marginal-1, ""); + } + else + cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword)); if (ys->ys_argument){ if (quotedstring(ys->ys_argument)) cprintf(cb, " \"%s\"", ys->ys_argument); @@ -974,7 +982,7 @@ ys_populate_range(yang_stmt *ys, &options, NULL, NULL, NULL, &fraction_digits) < 0) goto done; restype = yrestype?yrestype->ys_argument:NULL; - origtype = ytype_id((yang_stmt*)yparent); + origtype = yarg_id((yang_stmt*)yparent); /* This handles non-resolved also */ if (clicon_type2cv(origtype, restype, &cvtype) < 0) goto done; @@ -1291,8 +1299,8 @@ yang_expand(yang_node *yn) switch(ys->ys_keyword){ case Y_USES: /* Split argument into prefix and name */ - name = ytype_id(ys); /* This is uses/grouping name to resolve */ - prefix = ytype_prefix(ys); /* And this its prefix */ + name = yarg_id(ys); /* This is uses/grouping name to resolve */ + prefix = yarg_prefix(ys); /* And this its prefix */ if (ys_grouping_resolve(ys, prefix, name, &ygrouping) < 0) goto done; if (prefix) @@ -2016,6 +2024,8 @@ ys_parse(yang_stmt *ys, * Specific syntax checks and variable creation for stand-alone yang statements. * That is, siblings, etc, may not be there. Complete checks are made in * ys_populate instead. + * @param[in] ys yang statement + * @param[in] extra Yang extra for cornercases (unknown/extension) * * The cv:s created in parse-tree as follows: * fraction-digits : Create cv as uint8, check limits [1:8] (must be made in 1st pass) @@ -2023,13 +2033,19 @@ ys_parse(yang_stmt *ys, * @see ys_populate */ int -ys_parse_sub(yang_stmt *ys) +ys_parse_sub(yang_stmt *ys, + char *extra) { int retval = -1; - + int cvret; + char *reason = NULL; + yang_stmt *ymod; + uint8_t fd; + char *prefix = NULL; + char *name; + switch (ys->ys_keyword){ - case Y_FRACTION_DIGITS:{ - uint8_t fd; + case Y_FRACTION_DIGITS: if (ys_parse(ys, CGV_UINT8) == NULL) goto done; fd = cv_uint8_get(ys->ys_cv); @@ -2038,12 +2054,41 @@ ys_parse_sub(yang_stmt *ys) goto done; } break; - } + case Y_UNKNOWN: + if (extra == NULL) + break; + /* Find extension, if found, store it as unknown, if not, + break for error */ + prefix = yarg_prefix(ys); /* And this its prefix */ + name = yarg_id(ys); /* This is the type to resolve */ + if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) + goto ok; /* shouldnt happen */ + if (yang_find((yang_node*)ymod, Y_EXTENSION, name) == NULL){ + clicon_err(OE_YANG, errno, "Extension %s:%s not found", prefix, name); + goto done; + } + if ((ys->ys_cv = cv_new(CGV_STRING)) == NULL){ + clicon_err(OE_YANG, errno, "%s: cv_new", __FUNCTION__); + goto done; + } + if ((cvret = cv_parse1(extra, ys->ys_cv, &reason)) < 0){ /* error */ + clicon_err(OE_YANG, errno, "parsing cv"); + goto done; + } + if (cvret == 0){ /* parsing failed */ + clicon_err(OE_YANG, errno, "Parsing CV: %s", reason); + goto done; + } + free(extra); + break; default: break; } + ok: retval = 0; done: + if (prefix) + free(prefix); return retval; } diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index eb235115..8c80af65 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -101,12 +101,13 @@ clixon_yang_parsewrap(void) %s ESCAPE %s COMMENT1 %s COMMENT2 +%s UNKNOWN %% /* Common tokens */ [ \t] -<> { return MY_EOF; } -\n { _YY->yy_linenum++; } +<> { return MY_EOF; } +\n { _YY->yy_linenum++; } "/*" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT1); } "//" { _YY->yy_lex_state = YYSTATE; BEGIN(COMMENT2); } @@ -182,7 +183,14 @@ clixon_yang_parsewrap(void) : { return *yytext; } ; { return *yytext; } . { clixon_yang_parselval.string = strdup(yytext); - return CHAR;} + BEGIN(UNKNOWN); return CHAR; } + +: { return *yytext; } +; { BEGIN(KEYWORD); return *yytext; } +[ \t] { return ' '; } +. { clixon_yang_parselval.string = strdup(yytext); + return CHAR; } + ; { BEGIN(KEYWORD); return *yytext; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 68748c8a..b27f63ea 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -46,7 +46,6 @@ %token MY_EOF %token DQ /* Double quote: " */ -%token K_UNKNOWN /* template for error */ %token CHAR %type ustring @@ -239,12 +238,18 @@ ystack_push(struct clicon_yang_yacc_arg *yy, /*! Add a yang statement to existing top-of-stack. * - * Note: consumes 'argument' which assumes it is malloced and not freed by caller + * @param[in] yy Yang yacc argument + * @param[in] keyword Yang keyword + * @param[in] argument Yang argument + * @param[in] extra Yang extra for cornercases (unknown/extension) + + * @note consumes 'argument' and 'extra' which assumes it is malloced and not freed by caller */ static yang_stmt * ysp_add(struct clicon_yang_yacc_arg *yy, enum rfc_6020 keyword, - char *argument) + char *argument, + char *extra) { struct ys_stack *ystack = yy->yy_stack; yang_stmt *ys = NULL; @@ -262,7 +267,7 @@ ysp_add(struct clicon_yang_yacc_arg *yy, ys->ys_argument = argument; if (yn_insert(yn, ys) < 0) /* Insert into hierarchy */ goto err; - if (ys_parse_sub(ys) < 0) /* Check statement-specific syntax */ + if (ys_parse_sub(ys, extra) < 0) /* Check statement-specific syntax */ goto err2; /* dont free since part of tree */ // done: return ys; @@ -276,12 +281,12 @@ ysp_add(struct clicon_yang_yacc_arg *yy, /*! combination of ysp_add and ysp_push for sub-modules */ static yang_stmt * ysp_add_push(struct clicon_yang_yacc_arg *yy, - int keyword, + enum rfc_6020 keyword, char *argument) { yang_stmt *ys; - if ((ys = ysp_add(yy, keyword, argument)) == NULL) + if ((ys = ysp_add(yy, keyword, argument, NULL)) == NULL) return NULL; if (ystack_push(yy, (yang_node*)ys) == NULL) return NULL; @@ -330,12 +335,22 @@ file : module_stmt MY_EOF { clicon_debug(2,"file->submodule-stmt"); YYACCEPT; } ; -unknown_stmt : K_UNKNOWN { clixon_yang_parseerror(_yy, "unknown statement");clicon_debug(2,"unknown-stmt"); _YYERROR("0"); } - ; + /* For extensions */ +unknown_stmt : ustring ':' ustring ';' + { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + if (ysp_add(_yy, Y_UNKNOWN, id, NULL) == NULL) _YYERROR("0"); + clicon_debug(2,"unknown-stmt -> ustring : ustring"); + } + | ustring ':' ustring ' ' string ';' + { char *id; if ((id=prefix_id_join($1, $3)) == NULL) _YYERROR("0"); + if (ysp_add(_yy, Y_UNKNOWN, id, $5) == NULL) _YYERROR("0"); + clicon_debug(2,"unknown-stmt -> ustring : ustring string"); + } + ; /* module */ module_stmt : K_MODULE id_arg_str - { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); + { if ((_YY->yy_module = ysp_add_push(_yy, Y_MODULE, $2)) == NULL) _YYERROR("1"); } '{' module_substmts '}' { if (ystack_pop(_yy) < 0) _YYERROR("2"); @@ -359,7 +374,7 @@ module_substmt : module_header_stmts { clicon_debug(2,"module-substmt -> module- /* submodule */ submodule_stmt : K_SUBMODULE id_arg_str '{' submodule_substmts '}' - { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); + { if ((_YY->yy_module = ysp_add_push(_yy, Y_SUBMODULE, $2)) == NULL) _YYERROR("3"); clicon_debug(2,"submodule -> id-arg-str { submodule-stmts }"); } ; @@ -422,7 +437,7 @@ revision_stmts : revision_stmts revision_stmt ; revision_stmt : K_REVISION string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION, $2) == NULL) _YYERROR("4"); + { if (ysp_add(_yy, Y_REVISION, $2, NULL) == NULL) _YYERROR("4"); clicon_debug(2,"revision-stmt -> date-arg-str ;"); } | K_REVISION string { if (ysp_add_push(_yy, Y_REVISION, $2) == NULL) _YYERROR("5"); } @@ -482,7 +497,7 @@ data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> containe /* container */ container_stmt : K_CONTAINER id_arg_str ';' - { if (ysp_add(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("7"); + { if (ysp_add(_yy, Y_CONTAINER, $2, NULL) == NULL) _YYERROR("7"); clicon_debug(2,"container-stmt -> CONTAINER id-arg-str ;");} | K_CONTAINER id_arg_str { if (ysp_add_push(_yy, Y_CONTAINER, $2) == NULL) _YYERROR("8"); } @@ -512,7 +527,7 @@ container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when- /* leaf */ leaf_stmt : K_LEAF id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF, $2) == NULL) _YYERROR("10"); + { if (ysp_add(_yy, Y_LEAF, $2, NULL) == NULL) _YYERROR("10"); clicon_debug(2,"leaf-stmt -> LEAF id-arg-str ;");} | K_LEAF id_arg_str { if (ysp_add_push(_yy, Y_LEAF, $2) == NULL) _YYERROR("11"); } @@ -542,7 +557,7 @@ leaf_substmt : when_stmt { clicon_debug(2,"leaf-substmt -> when-stmt /* leaf-list */ leaf_list_stmt : K_LEAF_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("13"); + { if (ysp_add(_yy, Y_LEAF_LIST, $2, NULL) == NULL) _YYERROR("13"); clicon_debug(2,"leaf-list-stmt -> LEAF id-arg-str ;");} | K_LEAF_LIST id_arg_str { if (ysp_add_push(_yy, Y_LEAF_LIST, $2) == NULL) _YYERROR("14"); } @@ -573,7 +588,7 @@ leaf_list_substmt : when_stmt { clicon_debug(2,"leaf-list-substmt -> when /* list */ list_stmt : K_LIST id_arg_str ';' - { if (ysp_add(_yy, Y_LIST, $2) == NULL) _YYERROR("16"); + { if (ysp_add(_yy, Y_LIST, $2, NULL) == NULL) _YYERROR("16"); clicon_debug(2,"list-stmt -> LIST id-arg-str ;"); } | K_LIST id_arg_str { if (ysp_add_push(_yy, Y_LIST, $2) == NULL) _YYERROR("17"); } @@ -609,7 +624,7 @@ list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt /* choice */ choice_stmt : K_CHOICE id_arg_str ';' - { if (ysp_add(_yy, Y_CHOICE, $2) == NULL) _YYERROR("19"); + { if (ysp_add(_yy, Y_CHOICE, $2, NULL) == NULL) _YYERROR("19"); clicon_debug(2,"choice-stmt -> CHOICE id-arg-str ;"); } | K_CHOICE id_arg_str { if (ysp_add_push(_yy, Y_CHOICE, $2) == NULL) _YYERROR("20"); } @@ -648,7 +663,7 @@ short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> conta /* case */ case_stmt : K_CASE id_arg_str ';' - { if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("22"); + { if (ysp_add(_yy, Y_CASE, $2, NULL) == NULL) _YYERROR("22"); clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } | K_CASE id_arg_str { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } @@ -676,7 +691,7 @@ case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt /* anyxml */ anyxml_stmt : K_ANYXML id_arg_str ';' - { if (ysp_add(_yy, Y_ANYXML, $2) == NULL) _YYERROR("25"); + { if (ysp_add(_yy, Y_ANYXML, $2, NULL) == NULL) _YYERROR("25"); clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } | K_ANYXML id_arg_str { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } @@ -704,7 +719,7 @@ anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-st /* uses */ uses_stmt : K_USES identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("28"); + { if (ysp_add(_yy, Y_USES, $2, NULL) == NULL) _YYERROR("28"); clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } | K_USES identifier_ref_arg_str { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } @@ -732,7 +747,7 @@ uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt /* refine XXX need further refining */ refine_stmt : K_REFINE id_arg_str ';' - { if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("31"); + { if (ysp_add(_yy, Y_REFINE, $2, NULL) == NULL) _YYERROR("31"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } | K_REFINE id_arg_str { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } @@ -782,7 +797,7 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s /* when */ when_stmt : K_WHEN string ';' - { if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("36"); + { if (ysp_add(_yy, Y_WHEN, $2, NULL) == NULL) _YYERROR("36"); clicon_debug(2,"when-stmt -> WHEN string ;"); } | K_WHEN string { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } @@ -804,7 +819,7 @@ when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-s /* rpc */ rpc_stmt : K_RPC id_arg_str ';' - { if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("39"); + { if (ysp_add(_yy, Y_RPC, $2, NULL) == NULL) _YYERROR("39"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } | K_RPC id_arg_str { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } @@ -885,7 +900,7 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s /* Type */ type_stmt : K_TYPE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("48"); + { if (ysp_add(_yy, Y_TYPE, $2, NULL) == NULL) _YYERROR("48"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");} | K_TYPE identifier_ref_arg_str { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49"); @@ -953,7 +968,7 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st /* length-stmt */ length_stmt : K_LENGTH string ';' /* XXX length-arg-str */ - { if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("53"); + { if (ysp_add(_yy, Y_LENGTH, $2, NULL) == NULL) _YYERROR("53"); clicon_debug(2,"length-stmt -> LENGTH string ;"); } | K_LENGTH string @@ -978,7 +993,7 @@ length_substmt : error_message_stmt { clicon_debug(2,"length-substmt -> error-m /* Pattern */ pattern_stmt : K_PATTERN string ';' - { if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("56"); + { if (ysp_add(_yy, Y_PATTERN, $2, NULL) == NULL) _YYERROR("56"); clicon_debug(2,"pattern-stmt -> PATTERN string ;"); } | K_PATTERN string @@ -1002,7 +1017,7 @@ pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> refere /* Extension */ extension_stmt: K_EXTENSION id_arg_str ';' - { if (ysp_add(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("59"); + { if (ysp_add(_yy, Y_EXTENSION, $2, NULL) == NULL) _YYERROR("59"); clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } | K_EXTENSION id_arg_str { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } @@ -1033,7 +1048,7 @@ argument_stmt : K_ARGUMENT id_arg_str ';' /* Feature */ feature_stmt : K_FEATURE id_arg_str ';' - { if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("62"); + { if (ysp_add(_yy, Y_FEATURE, $2, NULL) == NULL) _YYERROR("62"); clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } | K_FEATURE id_arg_str { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } @@ -1059,7 +1074,7 @@ feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-fea /* Identity */ identity_stmt : K_IDENTITY string ';' /* XXX identifier-arg-str */ - { if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("65"); + { if (ysp_add(_yy, Y_IDENTITY, $2, NULL) == NULL) _YYERROR("65"); clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } | K_IDENTITY string @@ -1085,7 +1100,7 @@ identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base- /* range-stmt */ range_stmt : K_RANGE string ';' /* XXX range-arg-str */ - { if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("68"); + { if (ysp_add(_yy, Y_RANGE, $2, NULL) == NULL) _YYERROR("68"); clicon_debug(2,"range-stmt -> RANGE string ;"); } | K_RANGE string @@ -1110,7 +1125,7 @@ range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-me /* enum-stmt */ enum_stmt : K_ENUM string ';' - { if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("71"); + { if (ysp_add(_yy, Y_ENUM, $2, NULL) == NULL) _YYERROR("71"); clicon_debug(2,"enum-stmt -> ENUM string ;"); } | K_ENUM string { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("72"); } @@ -1135,7 +1150,7 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm /* bit-stmt */ bit_stmt : K_BIT string ';' - { if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("74"); + { if (ysp_add(_yy, Y_BIT, $2, NULL) == NULL) _YYERROR("74"); clicon_debug(2,"bit-stmt -> BIT string ;"); } | K_BIT string { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); } @@ -1159,7 +1174,7 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition /* mus-stmt */ must_stmt : K_MUST string ';' - { if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("77"); + { if (ysp_add(_yy, Y_MUST, $2, NULL) == NULL) _YYERROR("77"); clicon_debug(2,"must-stmt -> MUST string ;"); } | K_MUST string @@ -1183,7 +1198,7 @@ must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-mes /* error-message-stmt */ error_message_stmt : K_ERROR_MESSAGE string ';' - { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("80"); } + { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2, NULL) == NULL) _YYERROR("80"); } /* import */ import_stmt : K_IMPORT id_arg_str @@ -1206,144 +1221,144 @@ import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } /* Simple statements */ yang_version_stmt : K_YANG_VERSION string ';' /* XXX yang-version-arg-str */ - { if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("83"); + { if (ysp_add(_yy, Y_YANG_VERSION, $2, NULL) == NULL) _YYERROR("83"); clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } ; fraction_digits_stmt : K_FRACTION_DIGITS string ';' /* XXX: fraction-digits-arg-str */ - { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("84"); + { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2, NULL) == NULL) _YYERROR("84"); clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } ; if_feature_stmt : K_IF_FEATURE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("85"); + { if (ysp_add(_yy, Y_IF_FEATURE, $2, NULL) == NULL) _YYERROR("85"); clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } ; value_stmt : K_VALUE integer_value ';' - { if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("86"); + { if (ysp_add(_yy, Y_VALUE, $2, NULL) == NULL) _YYERROR("86"); clicon_debug(2,"value-stmt -> VALUE integer-value"); } ; position_stmt : K_POSITION integer_value ';' - { if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("87"); + { if (ysp_add(_yy, Y_POSITION, $2, NULL) == NULL) _YYERROR("87"); clicon_debug(2,"position-stmt -> POSITION integer-value"); } ; status_stmt : K_STATUS string ';' /* XXX: status-arg-str */ - { if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("88"); + { if (ysp_add(_yy, Y_STATUS, $2, NULL) == NULL) _YYERROR("88"); clicon_debug(2,"status-stmt -> STATUS string"); } ; config_stmt : K_CONFIG config_arg_str ';' - { if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("89"); + { if (ysp_add(_yy, Y_CONFIG, $2, NULL) == NULL) _YYERROR("89"); clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } ; base_stmt : K_BASE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("90"); + { if (ysp_add(_yy, Y_BASE, $2, NULL)== NULL) _YYERROR("90"); clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } ; path_stmt : K_PATH string ';' /* XXX: path-arg-str */ - { if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("91"); + { if (ysp_add(_yy, Y_PATH, $2, NULL)== NULL) _YYERROR("91"); clicon_debug(2,"path-stmt -> PATH string"); } ; require_instance_stmt : K_REQUIRE_INSTANCE string ';' /* XXX: require-instance-arg-str */ - { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("92"); + { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2, NULL)== NULL) _YYERROR("92"); clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } ; units_stmt : K_UNITS string ';' - { if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("93"); + { if (ysp_add(_yy, Y_UNITS, $2, NULL)== NULL) _YYERROR("93"); clicon_debug(2,"units-stmt -> UNITS string"); } ; default_stmt : K_DEFAULT string ';' - { if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("94"); + { if (ysp_add(_yy, Y_DEFAULT, $2, NULL)== NULL) _YYERROR("94"); clicon_debug(2,"default-stmt -> DEFAULT string"); } ; contact_stmt : K_CONTACT string ';' - { if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("95"); + { if (ysp_add(_yy, Y_CONTACT, $2, NULL)== NULL) _YYERROR("95"); clicon_debug(2,"contact-stmt -> CONTACT string"); } ; revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("96"); + { if (ysp_add(_yy, Y_REVISION_DATE, $2, NULL) == NULL) _YYERROR("96"); clicon_debug(2,"revision-date-stmt -> date;"); } ; include_stmt : K_INCLUDE id_arg_str ';' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("97"); + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("97"); clicon_debug(2,"include-stmt -> id-arg-str"); } | K_INCLUDE id_arg_str '{' revision_date_stmt '}' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("98"); + { if (ysp_add(_yy, Y_INCLUDE, $2, NULL)== NULL) _YYERROR("98"); clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } ; namespace_stmt : K_NAMESPACE string ';' /* XXX uri-str */ - { if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("99"); + { if (ysp_add(_yy, Y_NAMESPACE, $2, NULL)== NULL) _YYERROR("99"); clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } ; prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */ - { if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("100"); + { if (ysp_add(_yy, Y_PREFIX, $2, NULL)== NULL) _YYERROR("100"); clicon_debug(2,"prefix-stmt -> PREFIX string ;");} ; description_stmt: K_DESCRIPTION string ';' - { if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("101"); + { if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101"); clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} ; organization_stmt: K_ORGANIZATION string ';' - { if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("102"); + { if (ysp_add(_yy, Y_ORGANIZATION, $2, NULL)== NULL) _YYERROR("102"); clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} ; min_elements_stmt: K_MIN_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("103"); + { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2, NULL)== NULL) _YYERROR("103"); clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");} ; max_elements_stmt: K_MAX_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("104"); + { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2, NULL)== NULL) _YYERROR("104"); clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");} ; reference_stmt: K_REFERENCE string ';' - { if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("105"); + { if (ysp_add(_yy, Y_REFERENCE, $2, NULL)== NULL) _YYERROR("105"); clicon_debug(2,"reference-stmt -> REFERENCE string ;");} ; mandatory_stmt: K_MANDATORY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("106"); + if ((ys = ysp_add(_yy, Y_MANDATORY, $2, NULL))== NULL) _YYERROR("106"); clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} ; presence_stmt: K_PRESENCE string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("107"); + if ((ys = ysp_add(_yy, Y_PRESENCE, $2, NULL))== NULL) _YYERROR("107"); clicon_debug(2,"presence-stmt -> PRESENCE string ;");} ; ordered_by_stmt: K_ORDERED_BY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("108"); + if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2, NULL))== NULL) _YYERROR("108"); clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} ; key_stmt : K_KEY id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("109"); + { if (ysp_add(_yy, Y_KEY, $2, NULL)== NULL) _YYERROR("109"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; unique_stmt : K_UNIQUE id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("110"); + { if (ysp_add(_yy, Y_UNIQUE, $2, NULL)== NULL) _YYERROR("110"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 335e0ed6..2494cf5c 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -905,8 +905,8 @@ yang_type_resolve(yang_stmt *ys, if (options) *options = 0x0; *yrestype = NULL; /* Initialization of resolved type that may not be necessary */ - type = ytype_id(ytype); /* This is the type to resolve */ - prefix = ytype_prefix(ytype); /* And this its prefix */ + type = yarg_id(ytype); /* This is the type to resolve */ + prefix = yarg_prefix(ytype); /* And this its prefix */ /* Cache does not work for eg string length 32 */ if (!yang_builtin(type) && ytype->ys_typecache != NULL){ if (yang_type_cache_get(ytype->ys_typecache, @@ -1034,7 +1034,7 @@ yang_type_get(yang_stmt *ys, goto done; } /* XXX: here we seem to have some problems if type is union */ - type = ytype_id(ytype); + type = yarg_id(ytype); if (origtype) *origtype = type; if (yang_type_resolve(ys, ytype, yrestype, diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index f0ae519e..5e73ac31 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -13,11 +13,9 @@ cat < $cfg $cfg /usr/local/var $fyang - false /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - 1 + $dir/restconf.pidfile /usr/local/var/routing /usr/local/lib/xmldb/text.so @@ -25,7 +23,7 @@ EOF cat < $fyang module example{ - container interfaces-config{ + container cont1{ list interface{ key name; leaf name{ @@ -37,6 +35,11 @@ module example{ } } } + container cont2{ + leaf name{ + type string; + } + } } EOF @@ -62,56 +65,77 @@ sleep 1 new "restconf tests" + new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' new "restconf GET interface" -expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" '{"interface": \[{"name": "local0","type": "regular"}\]}' new "restconf GET if-type" -expectfn "curl -s -X GET http://localhost/restconf/data/interfaces-config/interface=local0/type" '{"type": "regular"}' +expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" '{"type": "regular"}' new "restconf POST interface" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' "" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" new "restconf POST again" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config' "Data resource already exis" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Data resource already exis" new "restconf POST from top" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists" new "restconf DELETE" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces-config' "" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" new "restconf GET null datastore" expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' new "restconf POST initial tree" -expectfn 'curl -s -X POST -d {"interfaces-config":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" + +new "restconf GET initial tree" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' + +new "restconf DELETE whole datastore" +expectfn 'curl -s -X DELETE http://localhost/restconf/data' "" + +new "restconf GET null datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": null}' new "restconf PUT initial datastore" -expectfn 'curl -s -X PUT -d {"data":{"interfaces-config":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "regular"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "regular"}\]}}}' + +new "restconf PUT replace datastore" +expectfn 'curl -s -X PUT -d {"data":{"cont2":{"name":"foo"}}} http://localhost/restconf/data' "" + +new "restconf GET replaced datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont2": {"name": "foo"}}}' + + +new "restconf PUT initial datastore again" +expectfn 'curl -s -X PUT -d {"data":{"cont1":{"interface":{"name":"local0","type":"regular"}}}} http://localhost/restconf/data' "" new "restconf PUT change interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/interfaces-config/interface=local0' "" +expectfn 'curl -s -X PUT -d {"interface":{"name":"local0","type":"atm0"}} http://localhost/restconf/data/cont1/interface=local0' "" + new "restconf GET datastore atm" -expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"interfaces-config": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' +expectfn "curl -s -X GET http://localhost/restconf/data" '{"data": {"cont1": {"interface": \[{"name": "local0","type": "atm0"}\]}}}' new "restconf PUT add interface" -expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/interfaces-config/interface=TEST' "" +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/interfaces-config/interface=TEST' "Bad request" +expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http://localhost/restconf/data/cont1/interface=TEST' "Bad request" new "restconf POST invalid no type" -expectfn 'curl -s -X POST -d {"interface":{"name":"ALPHA"}} http://localhost/restconf/data/interfaces-config' "Bad request" +expectfn 'curl -s -X POST -d {"interface":{"name":"ALPHA"}} http://localhost/restconf/data/cont1' "Bad request" new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_yang.sh b/test/test_yang.sh index 15eeba11..b53ad8d3 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -6,6 +6,7 @@ cfg=$dir/conf_yang.xml fyang=$dir/test.yang +fyangerr=$dir/err.yang cat < $cfg @@ -25,6 +26,12 @@ EOF cat < $fyang module example{ + prefix ex; + extension c-define { + description "Example from RFC 6020"; + argument "name"; + } + ex:c-define "MY_INTERFACES"; container x { list y { key "a b"; @@ -75,6 +82,17 @@ module example{ } EOF +# This yang definition uses an extension which is not defined. Error when loading +cat < $fyangerr +module example{ + prefix ex; + extension c-define { + description "Example from RFC 6020"; + argument "name"; + } + ex:not-defined ARGUMENT; +} +EOF # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg -y $fyang @@ -89,6 +107,13 @@ if [ $? -ne 0 ]; then err fi +new "cli defined extension" +expectfn "$clixon_cli -1f $cfg -y $fyang show version" "3." + +new "cli not defined extension" +# This text yields an error, but the test cannot detect the error message yet +#expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" "Yang error: Extension ex:not-defined not found" + new "netconf edit config" expecteof "$clixon_netconf -qf $cfg -y $fyang" "125]]>]]>" "^]]>]]>$" diff --git a/yang/ietf-netconf-acm@2012-02-22.yang b/yang/ietf-netconf-acm@2012-02-22.yang index 99ad961f..32888d51 100644 --- a/yang/ietf-netconf-acm@2012-02-22.yang +++ b/yang/ietf-netconf-acm@2012-02-22.yang @@ -194,7 +194,7 @@ module ietf-netconf-acm { */ container nacm { - /* nacm:default-deny-all; XXX How is this parsed ?? */ + nacm:default-deny-all; description "Parameters for NETCONF Access Control Model."; From 6a22524d38110480b4748cec843b98b2aff23db7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Feb 2018 20:09:11 +0100 Subject: [PATCH 06/50] Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config --- CHANGELOG.md | 2 +- datastore/text/clixon_xmldb_text.c | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f12492..09c863b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs - +* Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config ## 3.5.0 (12 February 2018) diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 739e2256..776de8d7 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -781,6 +781,12 @@ text_modify_top(cxobj *x0, break; } } + /* Special case top-level replace */ + if (op == OP_REPLACE || op == OP_DELETE){ + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) + xml_purge(x0c); + } /* Loop through children of the modification tree */ x1c = NULL; while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { From 990700b68da001545531cf664aff6c8e592216a4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 4 Mar 2018 21:24:38 +0100 Subject: [PATCH 07/50] https://github.com/clicon/clixon/issues/16 Added restconf/operations get, see RFC8040 Sec 3.3.2: --- CHANGELOG.md | 8 +- apps/cli/cli_common.c | 2 +- apps/cli/cli_show.c | 2 +- apps/restconf/restconf_main.c | 59 ++++++++------ apps/restconf/restconf_methods.c | 109 ++++++++++++++++++++++--- apps/restconf/restconf_methods.h | 14 ++-- datastore/keyvalue/clixon_keyvalue.c | 4 +- datastore/text/clixon_xmldb_text.c | 2 +- lib/clixon/clixon_xml_map.h | 2 +- lib/clixon/clixon_yang.h | 37 ++++++++- lib/src/clixon_json.c | 17 ++-- lib/src/clixon_xml_map.c | 24 +++--- lib/src/clixon_xml_sort.c | 2 +- lib/src/clixon_yang.c | 117 ++++++++++++++++++++++++--- test/test_restconf.sh | 48 ++++++++--- test/test_restconf2.sh | 1 - 16 files changed, 355 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c863b3..a300d032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,13 @@ # Clixon Changelog ## 3.6.0 (Upcoming) -### Major changes: -### Minor changes: -### Corrected Bugs -* Translate xml->json \n correctly + ### Major changes: ### Minor changes: +* yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE +* xml2json: include prefix in translation, so is translated to {"a:b" ..} * Use instead of when save/load configuration to file. This enables saved files to be used as datastore without any editing. Thanks Matt. @@ -21,6 +20,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs +* Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config ## 3.5.0 (12 February 2018) diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 0a869980..a4ec0130 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -236,7 +236,7 @@ cli_dbxml(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 13f58339..d3569aa6 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -171,7 +171,7 @@ expand_dbvar(void *h, /* This is primarily to get "y", * xpath2xml would have worked!! */ - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 5ae99543..a2be4db7 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -131,11 +131,12 @@ api_data(clicon_handle h, /*! Operations REST method, POST * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle - * @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] dvec Stream input data + * @param[in] data Stream input data + * @param[in] username Authenticated user */ static int api_operations(clicon_handle h, @@ -154,16 +155,42 @@ api_operations(clicon_handle h, request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); if (strcmp(request_method, "GET")==0) - retval = api_operation_get(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "POST")==0) - retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username); else retval = notfound(r); return retval; } +/*! Determine the root of the RESTCONF API + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle + * @note Hardcoded to "/restconf" + * Return see RFC8040 3.1 and RFC7320 + * In line with the best practices defined by [RFC7320], RESTCONF + * enables deployments to specify where the RESTCONF API is located. + */ +static int +api_well_known(clicon_handle h, + FCGX_Request *r) +{ + clicon_debug(1, "%s", __FUNCTION__); + FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, " \r\n"); + FCGX_FPrintF(r->out, "\r\n"); + + return 0; +} + /*! Retrieve the Top-Level API Resource + * @param[in] h Clicon handle + * @param[in] r Fastcgi request handle * @note Only returns null for operations and data,... + * See RFC8040 3.3 */ static int api_root(clicon_handle h, @@ -181,9 +208,11 @@ api_root(clicon_handle h, media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); if (strcmp(media_accept, "application/yang-data+xml")==0) use_xml++; + clicon_debug(1, "%s use-xml:%d media-accept:%s", __FUNCTION__, use_xml, media_accept); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); + if (xml_parse_string("2016-06-21", NULL, &xt) < 0) goto done; if ((cb = cbuf_new()) == NULL){ @@ -298,6 +327,8 @@ api_restconf(clicon_handle h, retval = notfound(r); goto done; } + test(r, 1); + if (pn == 2){ retval = api_root(h, r); goto done; @@ -319,7 +350,6 @@ api_restconf(clicon_handle h, if (str2cvec(data, '&', '=', &dvec) < 0) goto done; - test(r, 1); /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ @@ -367,23 +397,6 @@ api_restconf(clicon_handle h, return retval; } -/*! Process a FastCGI request - * @param[in] r Fastcgi request handle - */ -static int -api_well_known(clicon_handle h, - FCGX_Request *r) -{ - clicon_debug(1, "%s", __FUNCTION__); - FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, " \r\n"); - FCGX_FPrintF(r->out, "\r\n"); - - return 0; -} static int restconf_terminate(clicon_handle h) @@ -547,7 +560,7 @@ main(int argc, if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0) api_restconf(h, r); /* This is the function */ else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) { - api_well_known(h, r); /* This is the function */ + api_well_known(h, r); /* */ } else{ clicon_debug(1, "top-level %s not found", path); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 992a8662..1bef74fc 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -195,6 +195,7 @@ api_data_get_err(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user * @param[in] head If 1 is HEAD, otherwise GET * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 @@ -332,6 +333,7 @@ api_data_get2(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user The HEAD method is sent by the client to retrieve just the header fields that would be returned for the comparable GET method, without the response message-body. @@ -355,6 +357,7 @@ api_data_head(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] username Authenticated user * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode @@ -390,6 +393,8 @@ api_data_get(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user + * @note restconf POST is mapped to edit-config create. POST: target resource type is datastore --> create a top-level resource @@ -461,7 +466,7 @@ api_data_post(clicon_handle h, goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -586,6 +591,7 @@ match_list_keys(yang_stmt *y, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user * @note restconf PUT is mapped to edit-config replace. * @example curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 @@ -651,7 +657,7 @@ api_data_put(clicon_handle h, if (xml_value_set(xu, username) < 0) goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -749,6 +755,7 @@ api_data_put(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] username Authenticated user * Netconf: (nc:operation="merge") */ int @@ -769,6 +776,7 @@ api_data_patch(clicon_handle h, * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) + * @param[in] username Authenticated user * @param[in] pi Offset, where path starts * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 @@ -812,7 +820,7 @@ api_data_delete(clicon_handle h, if (xml_value_set(xu, username) < 0) goto done; } - if (api_path && api_path2xml(api_path, yspec, xtop, 0, &xbot, &y) < 0) + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; @@ -851,10 +859,27 @@ api_data_delete(clicon_handle h, return retval; } -/*! NYI +/*! GET restconf/operations resource + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) + * @param[in] pcvec Vector of path ie DOCUMENT_URI element + * @param[in] pi Offset, where path starts + * @param[in] qvec Vector of query string (QUERY_STRING) + * @param[in] data Stream input data + * @param[in] username Authenticated user + * + * @code + * curl -G http://localhost/restconf/operations + * @endcode + * RFC8040 Sec 3.3.2: + * This optional resource is a container that provides access to the + * data-model-specific RPC operations supported by the server. The + * server MAY omit this resource if no data-model-specific RPC + * operations are advertised. */ int -api_operation_get(clicon_handle h, +api_operations_get(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, @@ -863,23 +888,87 @@ api_operation_get(clicon_handle h, char *data, char *username) { - notimplemented(r); - return 0; + int retval = -1; + int pretty; + char *media_accept; + int use_xml = 0; /* By default use JSON */ + yang_spec *yspec; + yang_stmt *ym; + yang_stmt *yc; + yang_stmt *yprefix; + char *prefix; + cbuf *cbx = NULL; + cxobj *xt = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + yspec = clicon_dbspec_yang(h); + if ((cbx = cbuf_new()) == NULL) + goto done; + cprintf(cbx, ""); + ym = NULL; + while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { + if ((yprefix = yang_find((yang_node*)ym, Y_PREFIX, NULL)) != NULL) + prefix = yprefix->ys_argument; + else + continue; + yc = NULL; + while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { + if (yc->ys_keyword != Y_RPC) + continue; + cprintf(cbx, "<%s:%s />", prefix, yc->ys_argument); + } + } + cprintf(cbx, ""); + clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx)); + if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) + goto done; + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + cbuf_reset(cbx); /* reuse same cbuf */ + if (use_xml){ + if (clicon_xml2cbuf(cbx, xt, 0, pretty) < 0) /* Dont print top object? */ + goto done; + } + else{ + if (xml2json_cbuf(cbx, xt, pretty) < 0) + goto done; + } + clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); + FCGX_SetExitStatus(200, r->out); /* OK */ + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); + FCGX_FPrintF(r->out, "\r\n\r\n"); + // ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cbx) + cbuf_free(cbx); + if (xt) + xml_free(xt); + return retval; } /*! REST operation POST method * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle + * @param[in] path According to restconf (Sec 3.5.1.1 in [draft]) * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @note We map post to edit-config create. + * @param[in] username Authenticated user + * @note We map post to edit-config create. POST {+restconf}/operations/ */ int -api_operation_post(clicon_handle h, +api_operations_post(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, @@ -956,7 +1045,7 @@ api_operation_post(clicon_handle h, } /* XXX: something strange for rpc user */ - if (api_path2xml(oppath, yspec, xtop, 1, &xbot, &y) < 0) + if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; #if 1 { diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index b50b91a6..246552b2 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -61,13 +61,13 @@ int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, char *username); -int api_operation_get(clicon_handle h, FCGX_Request *r, - char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, char *username); - -int api_operation_post(clicon_handle h, FCGX_Request *r, +int api_operations_get(clicon_handle h, FCGX_Request *r, char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, - char *username); + cvec *pcvec, int pi, cvec *qvec, char *data, char *username); + +int api_operations_post(clicon_handle h, FCGX_Request *r, + char *path, + cvec *pcvec, int pi, cvec *qvec, char *data, + char *username); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 018c86fd..c1ad1911 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -312,7 +312,7 @@ get(char *dbname, restval++; } if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name, 0)) == NULL){ + if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -799,7 +799,7 @@ kv_put(xmldb_handle xh, } // clicon_log(LOG_WARNING, "%s", __FUNCTION__); while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((ys = yang_find_topnode(yspec, xml_name(x), 0)) == NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x), YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); goto done; } diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 776de8d7..b9eddc5a 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -792,7 +792,7 @@ text_modify_top(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6d3befc3..71cb0e46 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -64,7 +64,7 @@ int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, - int schemanode, cxobj **xpathp, yang_node **ypathp); + yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index c89a8f80..589bfb04 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -123,14 +123,45 @@ enum rfc_6020{ Y_SPEC /* XXX: NOTE NOT YANG STATEMENT, reserved for top level spec */ }; +/* Type used to group yang nodes used in some functions + * See RFC7950 Sec 3 + */ +enum yang_class{ + YC_NONE, /* Someting else,... */ + YC_DATANODE, /* See yang_datanode() */ + YC_DATADEFINITION, /* See yang_datadefinition() */ + YC_SCHEMANODE /* See yang_schemanode() */ +}; +typedef enum yang_class yang_class; + #define YANG_FLAG_MARK 0x01 /* Marker for dynamic algorithms, eg expand */ -/* Yang data node */ +/* Yang data node + * See RFC7950 Sec 3: + * o data node: A node in the schema tree that can be instantiated in a + * data tree. One of container, leaf, leaf-list, list, anydata, and + * anyxml. + */ #define yang_datanode(y) ((y)->ys_keyword == Y_CONTAINER || (y)->ys_keyword == Y_LEAF || (y)->ys_keyword == Y_LIST || (y)->ys_keyword == Y_LEAF_LIST || (y)->ys_keyword == Y_ANYXML) -/* Yang schema node */ +/* Yang data definition statement + * See RFC 7950 Sec 3: + * o data definition statement: A statement that defines new data + * nodes. One of "container", "leaf", "leaf-list", "list", "choice", + * "case", "augment", "uses", "anydata", and "anyxml". + */ +#define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES) + + +/* Yang schema node . + * See RFC 7950 Sec 3: + * o schema node: A node in the schema tree. One of action, container, + * leaf, leaf-list, list, choice, case, rpc, input, output, + * notification, anydata, and anyxml. + */ #define yang_schemanode(y) (yang_datanode(y) || (y)->ys_keyword == Y_RPC || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_INPUT || (y)->ys_keyword == Y_OUTPUT || (y)->ys_keyword == Y_NOTIFICATION) + typedef struct yang_stmt yang_stmt; /* forward */ /*! Yang type cache. Yang type statements can cache all typedef info here @@ -217,7 +248,7 @@ yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); -yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, int schemanode); +yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index e9ac79b5..b14592cc 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -304,10 +304,12 @@ xml2json1_cbuf(cbuf *cb, break; } case NO_ARRAY: - if (!flat) - cprintf(cb, "%*s\"%s\": ", - pretty?(level*JSON_INDENT):0, "", - xml_name(x)); + if (!flat){ + cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s\": ", xml_name(x)); + } switch (childt){ case NULL_CHILD: cprintf(cb, "null"); @@ -323,9 +325,10 @@ xml2json1_cbuf(cbuf *cb, break; case FIRST_ARRAY: case SINGLE_ARRAY: - cprintf(cb, "%*s\"%s\": ", - pretty?(level*JSON_INDENT):0, "", - xml_name(x)); + cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s\": ", xml_name(x)); level++; cprintf(cb, "[%s%*s", pretty?"\n":"", diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 105b8661..5f2e6b7b 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -587,7 +587,7 @@ yang_next(yang_node *y, yang_stmt *ys; if (y->yn_keyword == Y_SPEC) - ys = yang_find_topnode((yang_spec*)y, name, 0); + ys = yang_find_topnode((yang_spec*)y, name, YC_DATANODE); else ys = yang_find_datanode(y, name); if (ys == NULL) @@ -1313,7 +1313,7 @@ xml_spec_populate(cxobj *x, (yp = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yp, xml_name(x)); else - y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ #endif if (y) xml_spec_set(x, y); @@ -1367,7 +1367,7 @@ api_path2xpath_cvv(yang_spec *yspec, clicon_debug(1, "[%d] cvname:%s", i, name); clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0)); if (i == offset){ - if ((y = yang_find_topnode(yspec, name, 0)) == NULL){ + if ((y = yang_find_topnode(yspec, name, YC_DATANODE)) == NULL){ clicon_err(OE_UNIX, errno, "No yang node found: %s", name); goto done; } @@ -1448,7 +1448,7 @@ api_path2xpath(yang_spec *yspec, * @param[in] nvec Length of vec * @param[in] x0 Xpath tree so far * @param[in] y0 Yang spec for x0 - * @param[in] schemanode If set use schema nodes otherwise data nodes. + * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xpathp Resulting xml tree * @param[out] ypathp Yang spec matching xpathp * @see api_path2xml @@ -1458,7 +1458,7 @@ api_path2xml_vec(char **vec, int nvec, cxobj *x0, yang_node *y0, - int schemanode, + yang_class nodeclass, cxobj **xpathp, yang_node **ypathp) { @@ -1500,10 +1500,10 @@ api_path2xml_vec(char **vec, name = local; } if (y0->yn_keyword == Y_SPEC){ /* top-node */ - y = yang_find_topnode((yang_spec*)y0, name, schemanode); + y = yang_find_topnode((yang_spec*)y0, name, nodeclass); } else { - y = schemanode?yang_find_schemanode((yang_node*)y0, name): + y = (nodeclass==YC_SCHEMANODE)?yang_find_schemanode((yang_node*)y0, name): yang_find_datanode((yang_node*)y0, name); } if (y == NULL){ @@ -1572,7 +1572,7 @@ api_path2xml_vec(char **vec, } if (api_path2xml_vec(vec+1, nvec-1, x, (yang_node*)y, - schemanode, + nodeclass, xpathp, ypathp) < 0) goto done; retval = 0; @@ -1588,7 +1588,7 @@ api_path2xml_vec(char **vec, * @param[in] api_path API-path as defined in RFC 8040 * @param[in] yspec Yang spec * @param[in,out] xtop Incoming XML tree - * @param[in] schemanode If set use schema nodes otherwise data nodes. + * @param[in] nodeclass Set to schema nodes, data nodes, etc * @param[out] xbotp Resulting xml tree (end of xpath) * @param[out] ybotp Yang spec matching xbotp * @example @@ -1605,7 +1605,7 @@ int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, - int schemanode, + yang_class nodeclass, cxobj **xbotp, yang_node **ybotp) { @@ -1628,7 +1628,7 @@ api_path2xml(char *api_path, } nvec--; /* NULL-terminated */ if (api_path2xml_vec(vec+1, nvec, - xtop, (yang_node*)yspec, schemanode, + xtop, (yang_node*)yspec, nodeclass, xbotp, ybotp) < 0) goto done; retval = 0; @@ -1736,7 +1736,7 @@ xml_merge(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, 0)) == NULL){ + if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index ed2e987e..0016a59b 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -93,7 +93,7 @@ xml_child_spec(char *name, if (xp && (yparent = xml_spec(xp)) != NULL) y = yang_find_datanode((yang_node*)yparent, name); else if (yspec) - y = yang_find_topnode(yspec, name, 0); /* still NULL for config */ + y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ else y = NULL; *yresult = y; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index ba3758b7..2ecae84b 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -402,6 +402,94 @@ yang_find(yang_node *yn, } return match ? ys : NULL; } +#ifdef NOTYET +/*! Prototype more generic than yang_find_datanode and yang_find_schemanode + */ +yang_stmt * +yang_find_class(yang_node *yn, + char *argument, + yang_class class) +{ + yang_stmt *ys = NULL; + yang_stmt *yc = NULL; + yang_stmt *ysmatch = NULL; + int i, j; + int ok; + + for (i=0; iyn_len; i++){ + ys = yn->yn_stmt[i]; + switch(class){ + case YC_NONE: + ok = 1; + break; + case YC_DATANODE: + ok = yang_datanode(ys); + break; + case YC_DATADEFINITION: + ok = yang_datadefinition(ys); + break; + case YC_SCHEMANODE: + ok = yang_schemanode(ys); + break; + } + if (!ok) + continue; + switch(class){ + case YC_NONE: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_DATANODE: + case YC_DATADEFINITION: + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + break; + case YC_SCHEMANODE: + if (ys->ys_keyword == Y_CHOICE){ /* Look for its children */ + for (j=0; jys_len; j++){ + yc = ys->ys_stmt[j]; + if (yc->ys_keyword == Y_CASE) /* Look for its children */ + ysmatch = yang_find_class((yang_node*)yc, argument, class); + else{ + if (yang_schemanode(yc)){ + if (argument == NULL) + ysmatch = yc; + else + if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0) + ysmatch = yc; + } + } + if (ysmatch) + goto match; + } + } /* Y_CHOICE */ + else{ + if (argument == NULL) + ysmatch = ys; + else + if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0) + ysmatch = ys; + if (ysmatch) + goto match; + + } + break; + } /* switch */ + } /* for */ + match: + return ysmatch; +} +#endif /* NOTYET */ /*! Find child data node with matching argument (container, leaf, etc) * @@ -455,6 +543,8 @@ yang_find_datanode(yang_node *yn, } /*! Find child schema node with matching argument (container, leaf, etc) + * @param[in] yn Yang node, current context node. + * @param[in] argument if NULL, match any(first) argument. * @note XXX unify code with yang_find_datanode? * @see yang_find_datanode */ @@ -505,7 +595,7 @@ yang_find_schemanode(yang_node *yn, /*! Find first matching data node in all (sub)modules in a yang spec * * @param[in] ysp Yang specification - * @param[in] name if NULL, match any(first) argument. XXX is that really a case? + * @param[in] argument if NULL, match any(first) argument. XXX is that really a case? * @param[in] schemanode If set look for schema nodes, otherwise only data nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to @@ -514,8 +604,8 @@ yang_find_schemanode(yang_node *yn, */ yang_stmt * yang_find_topnode(yang_spec *ysp, - char *name, - int schemanode) + char *argument, + yang_class class) { yang_stmt *ys = NULL; yang_stmt *yc = NULL; @@ -523,13 +613,22 @@ yang_find_topnode(yang_spec *ysp, for (i=0; iyp_len; i++){ ys = ysp->yp_stmt[i]; - if (schemanode){ - if ((yc = yang_find_schemanode((yang_node*)ys, name)) != NULL) + switch (class){ + case YC_NONE: + if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL) return yc; + break; + case YC_DATANODE: + if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_SCHEMANODE: + if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL) + return yc; + break; + case YC_DATADEFINITION: + break; /* nyi */ } - else - if ((yc = yang_find_datanode((yang_node*)ys, name)) != NULL) - return yc; } return NULL; } @@ -1929,7 +2028,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, } if (ymod == NULL){ /* Try with topnode */ yang_stmt *ys; - if ((ys = yang_find_topnode(yspec, id, 1)) == NULL){ + if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); goto done; } diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c1a25263..61073308 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -78,22 +78,49 @@ sleep 1 new "restconf tests" -new "restconf options" -expectfn "curl -i -sS -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" - -new "restconf head" -expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" -#Content-Type: application/yang-data+json" - -new "restconf root discovery" +new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "" -new "restconf get restconf json" +new "restconf get restconf resource. RFC 8040 3.3 (json)" expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' -new "restconf get restconf/yang-library-version json" +new "restconf get restconf resource. RFC 8040 3.3 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) +expect="2016-06-21" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf get restconf/operations. RFC8040 3.3.2" +expectfn "curl -sG http://localhost/restconf/operations" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}' + +new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) +expect="" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf get restconf/yang-library-version. RFC8040 3.3.3" expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}' +new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" +ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) +expect="2016-06-21" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf options. RFC 8040 4.1" +expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE" + +new "restconf head. RFC 8040 4.2" +expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" +#Content-Type: application/yang-data+json" + new "restconf empty rpc" expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}' @@ -101,6 +128,7 @@ new "restconf get empty config + state json" expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" new "restconf get empty config + state xml" +# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) expect="eth0eth42" match=`echo $ret | grep -EZo "$expect"` diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 5e73ac31..cdd2d309 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -65,7 +65,6 @@ sleep 1 new "restconf tests" - new "restconf POST initial tree" expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"local0","type":"regular"}}} http://localhost/restconf/data' "" From ac7480b9f52840a215a7e76993c4dd37ae9690ec Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Mon, 5 Mar 2018 19:59:43 +0000 Subject: [PATCH 08/50] * Invalid key to api_path2xml gives warning instead of error and quit. --- CHANGELOG.md | 1 + apps/cli/cli_show.c | 3 +++ lib/src/clixon_xml_map.c | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a300d032..45fdb979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Major changes: ### Minor changes: +* Invalid key to api_path2xml gives warning instead of error and quit. * yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE * xml2json: include prefix in translation, so is translated to {"a:b" ..} diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index d3569aa6..7106965f 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -173,6 +173,8 @@ expand_dbvar(void *h, */ if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; + if (y==NULL) + goto ok; /* Special case for leafref. Detect leafref via Yang-type, * Get Yang path element, tentatively add the new syntax to the whole * tree and apply the path to that. @@ -236,6 +238,7 @@ expand_dbvar(void *h, /* XXX RFC3986 decode */ cvec_add_string(commands, NULL, bodystr); } + ok: retval = 0; done: if (api_path) diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 5f2e6b7b..577fd298 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1614,8 +1614,8 @@ api_path2xml(char *api_path, int nvec; if (*api_path!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", api_path); - goto done; + clicon_log(LOG_WARNING, "Invalid key: %s (must start with '/')", api_path); + goto ok; } if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL) goto done; @@ -1631,6 +1631,7 @@ api_path2xml(char *api_path, xtop, (yang_node*)yspec, nodeclass, xbotp, ybotp) < 0) goto done; + ok: retval = 0; done: if (vec) From 695aef2cb6efc4812e07e5cd400fe00182afbefa Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 7 Mar 2018 21:12:10 +0100 Subject: [PATCH 09/50] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45fdb979..7ac9f2a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Major changes: ### Minor changes: * Invalid key to api_path2xml gives warning instead of error and quit. +* Added restconf/operations get, see RFC8040 Sec 3.3.2: * yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE * xml2json: include prefix in translation, so is translated to {"a:b" ..} From 11e2b91a051bad4bcf002989709c46efdb2ec116 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Wed, 7 Mar 2018 22:33:47 +0100 Subject: [PATCH 10/50] Experimental: Added CLICON_TRANSACTION_MOD configurqation option. If set, modifications in validation and commit callbacks are written back into the datastore. --- CHANGELOG.md | 3 +++ apps/backend/backend_commit.c | 13 ++++++++++++- yang/clixon-config@2018-02-12.yang | 6 ++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ac9f2a9..ef8aad59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Major changes: ### Minor changes: +* Experimental: Added CLICON_TRANSACTION_MOD configurqation option. If set, + modifications in validation and commit callbacks are written back + into the datastore. * Invalid key to api_path2xml gives warning instead of error and quit. * Added restconf/operations get, see RFC8040 Sec 3.3.2: * yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 73753b63..ef73adf2 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -239,13 +239,20 @@ candidate_commit(clicon_handle h, if (plugin_transaction_commit(h, td) < 0) goto done; - /* 8. Success: Copy candidate to running */ + /* Optionally write (potentially modified) tree back to candidate */ + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) + if (xmldb_put(h, candidate, OP_REPLACE, td->td_target) < 0) + goto done; + /* 8. Success: Copy candidate to running + */ + if (xmldb_copy(h, candidate, "running") < 0) goto done; /* 9. Call plugin transaction end callbacks */ plugin_transaction_end(h, td); + /* 8. Copy running back to candidate in case end functions updated running */ if (xmldb_copy(h, "running", candidate) < 0){ /* ignore errors or signal major setback ? */ @@ -392,6 +399,10 @@ from_client_validate(clicon_handle h, clicon_err_reason); goto ok; } + /* Optionally write (potentially modified) tree back to candidate */ + if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) + if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target) < 0) + goto done; cprintf(cbret, ""); ok: retval = 0; diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 883a002f..3bcc0ce2 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -300,5 +300,11 @@ module clixon-config { type startup_mode; description "Which method to boot/start clicon backend"; } + leaf CLICON_TRANSACTION_MOD { + type boolean; + default false; + description "If set, modifications in validation and commit + callbacks will be saved into running"; + } } } From 0a11445963e13fb8f94c84f39f19bcf526e1ce2c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 10 Mar 2018 12:35:11 +0100 Subject: [PATCH 11/50] cprintf sanity checks --- apps/cli/cli_plugin.c | 1 - apps/netconf/netconf_hello.c | 2 +- lib/src/clixon_proto_client.c | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index f2da6b78..dd399950 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -854,7 +854,6 @@ prompt_fmt (char *prompt, size_t plen, char *fmt, ...) } /* Start with empty string */ - cprintf(cb, ""); while(*s) { if (*s == '%' && *++s) { switch(*s) { diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index 68b42805..833a1c5c 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -122,7 +122,7 @@ netconf_create_hello(cbuf *xf, /* msg buffer */ cprintf(xf, "urn:ietf:params:netconf:capability:notification:1.0\n"); cprintf(xf, "urn:ietf:params:netconf:capability:startup:1.0\n"); cprintf(xf, ""); - cprintf(xf, "%lu", 42+session_id); + cprintf(xf, "%lu", (long unsigned int)42+session_id); cprintf(xf, ""); add_postamble(xf); return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 05991b74..9ba18073 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -243,7 +243,7 @@ clicon_rpc_generate_error(char *format, * @retval -1 Error, fatal or xml * @code * cxobj *xt = NULL; - * if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) + * if (clicon_rpc_get_config(h, "running", "/", username, &xt) < 0) * err; * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ * clicon_rpc_generate_error("", xerr); From 859d424ea369de223b8cd7f4360f077c8c37b0fe Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 11 Mar 2018 20:17:11 +0100 Subject: [PATCH 12/50] (Work in progress) Restconf error handling for get and edit operations --- CHANGELOG.md | 8 ++- apps/cli/cli_generate.c | 1 - apps/restconf/restconf_methods.c | 87 ++++++++++++++++++++---------- lib/src/clixon_proto_client.c | 36 ++++++++++--- test/clixon | 73 ------------------------- test/test_restconf.sh | 18 +++++-- test/test_restconf2.sh | 4 +- test/test_type.sh | 55 ++++++++++++++++++- yang/clixon-config@2018-02-12.yang | 2 +- 9 files changed, 165 insertions(+), 119 deletions(-) delete mode 100755 test/clixon diff --git a/CHANGELOG.md b/CHANGELOG.md index ef8aad59..ee315073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ ### Major changes: +* (Work in progress) Restconf error handling for get and edit operations + ### Minor changes: +* Add username to rpc calls to prepare for authorization for backend: + clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) + clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) + * Experimental: Added CLICON_TRANSACTION_MOD configurqation option. If set, modifications in validation and commit callbacks are written back into the datastore. @@ -31,7 +37,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. ## 3.5.0 (12 February 2018) ### Major changes: -* Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right. +* Major Restconf feature update to comply to RFC 8040. Thanks Stephen Jones for getting right. * GET: Always return object referenced (and nothing else). ie, GET /restconf/data/X returns X. * GET Added support for the following resources: Well-known, top-level resource, and yang library version, * GET Single element JSON lists use {list:[element]}, not {list:element}. diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 023b531d..67fa9cec 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -238,7 +238,6 @@ yang2cli_var_sub(clicon_handle h, goto done; } } - else{ /* Cligen does not have 'max' keyword in range so need to find actual max value of type if yang range expression is 0..max */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 1bef74fc..5ff69548 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -144,47 +144,62 @@ api_data_options(clicon_handle h, * @param[in] h Clixon handle * @param[in] r Fastcgi request handle * @param[in] xerr XML error message from backend + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML */ static int -api_data_get_err(clicon_handle h, - FCGX_Request *r, - cxobj *xerr) +api_return_err(clicon_handle h, + FCGX_Request *r, + cxobj *xerr, + int pretty, + int use_xml) { int retval = -1; - cbuf *cbj = NULL; + cbuf *cb = NULL; cxobj *xtag; int code; const char *reason_phrase; - if ((cbj = cbuf_new()) == NULL) + clicon_debug(1, "%s", __FUNCTION__); + if ((cb = cbuf_new()) == NULL) goto done; - if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ + if ((xtag = xpath_first(xerr, "error-tag")) == NULL){ notfound(r); /* bad reply? */ goto ok; } code = restconf_err2code(xml_body(xtag)); if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; - clicon_debug(1, "%s code:%d reason phrase:%s", - __FUNCTION__, code, reason_phrase); - if (xml_name_set(xerr, "error") < 0) goto done; - if (xml2json_cbuf(cbj, xerr, 1) < 0) - goto done; + if (use_xml){ + if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0) + goto done; + } + else + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n"); - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, "{\r\n"); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n"); - FCGX_FPrintF(r->out, " %s", cbuf_get(cbj)); - FCGX_FPrintF(r->out, " }\r\n"); - FCGX_FPrintF(r->out, "}\r\n"); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", + use_xml?"xml":"json"); + if (use_xml){ + FCGX_FPrintF(r->out, " %s", cbuf_get(cb), pretty?"\r\n":""); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " \r\n"); + } + else{ + FCGX_FPrintF(r->out, "{%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, " %s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " }%s", pretty?"\r\n":""); + FCGX_FPrintF(r->out, "}\r\n"); + } ok: retval = 0; done: - if (cbj) - cbuf_free(cbj); + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); return retval; } @@ -269,9 +284,9 @@ api_data_get2(clicon_handle h, cbuf_free(cb); } #endif - /* Check if error return */ + /* Check if error return XXX this needs more work */ if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ - if (api_data_get_err(h, r, xerr) < 0) + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; } @@ -425,6 +440,7 @@ api_data_post(clicon_handle h, { int retval = -1; enum operation_type op = OP_CREATE; + int pretty; int i; cxobj *xdata = NULL; cbuf *cbx = NULL; @@ -437,10 +453,18 @@ api_data_post(clicon_handle h, cxobj *xu; char *media_content_type; int parse_xml = 0; /* By default expect and parse JSON */ - + cxobj *xret = NULL; + cxobj *xerr; + char *media_accept; + int use_xml = 0; /* By default use JSON */ + clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); if (media_content_type && strcmp(media_content_type, "application/yang-data+xml")==0) @@ -499,14 +523,19 @@ api_data_post(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; + + cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - conflict(r); - goto ok; + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto done; } /* Assume this is validation failed since commit includes validate */ if (clicon_rpc_commit(h) < 0){ @@ -522,6 +551,8 @@ api_data_post(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); if (xtop) xml_free(xtop); if (xdata) diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9ba18073..cb150c0d 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -200,7 +200,7 @@ clicon_rpc_netconf_xml(clicon_handle h, return retval; } -/*! Generate clicon error function call from Netconf error message +/*! Generate and log clicon error function call from Netconf error message * @param[in] xerr Netconf error message on the level: */ int @@ -308,7 +308,7 @@ clicon_rpc_get_config(clicon_handle h, * @param[in] op Operation on database item: OP_MERGE, OP_REPLACE * @param[in] xml XML string. Ex: ..... * @retval 0 OK - * @retval -1 Error + * @retval -1 Error and logged to syslog * @note xml arg need to have as top element * @code * if (clicon_rpc_edit_config(h, "running", OP_MERGE, @@ -361,6 +361,8 @@ clicon_rpc_edit_config(clicon_handle h, * @param[in] h CLICON handle * @param[in] db1 src database, eg "running" * @param[in] db2 dst database, eg "startup" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_copy_config(h, "running", "startup") < 0) * err; @@ -396,6 +398,8 @@ clicon_rpc_copy_config(clicon_handle h, /*! Send a request to backend to delete a config database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog * @code * if (clicon_rpc_delete_config(h, "startup") < 0) * err; @@ -430,6 +434,8 @@ clicon_rpc_delete_config(clicon_handle h, /*! Lock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_lock(clicon_handle h, @@ -460,6 +466,8 @@ clicon_rpc_lock(clicon_handle h, /*! Unlock a database * @param[in] h CLICON handle * @param[in] db database, eg "running" + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_unlock(clicon_handle h, @@ -557,6 +565,8 @@ clicon_rpc_get(clicon_handle h, /*! Close a (user) session * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_close_session(clicon_handle h) @@ -586,6 +596,8 @@ clicon_rpc_close_session(clicon_handle h) /*! Kill other user sessions * @param[in] h CLICON handle * @param[in] session_id Session id of other user session + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_kill_session(clicon_handle h, @@ -616,7 +628,8 @@ clicon_rpc_kill_session(clicon_handle h, /*! Send validate request to backend daemon * @param[in] h CLICON handle * @param[in] db Name of database - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_validate(clicon_handle h, @@ -646,7 +659,8 @@ clicon_rpc_validate(clicon_handle h, /*! Commit changes send a commit request to backend daemon * @param[in] h CLICON handle - * @retval 0 OK + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_commit(clicon_handle h) @@ -674,8 +688,9 @@ clicon_rpc_commit(clicon_handle h) } /*! Discard all changes in candidate / revert to running - * @param[in] h CLICON handle - * @retval 0 OK + * @param[in] h CLICON handle + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_discard_changes(clicon_handle h) @@ -707,6 +722,9 @@ clicon_rpc_discard_changes(clicon_handle h) * @param{in] stream name of notificatio/log stream (CLICON is predefined) * @param{in] filter message filter, eg xpath for xml notifications * @param[out] s0 socket returned where notification mesages will appear + * @retval 0 OK + * @retval -1 Error and logged to syslog + * @note When using netconf create-subsrciption,status and format is not supported */ int @@ -742,8 +760,10 @@ clicon_rpc_create_subscription(clicon_handle h, } /*! Send a debug request to backend server - * @param[in] h CLICON handle - * @param[in] level Debug level + * @param[in] h CLICON handle + * @param[in] level Debug level + * @retval 0 OK + * @retval -1 Error and logged to syslog */ int clicon_rpc_debug(clicon_handle h, diff --git a/test/clixon b/test/clixon deleted file mode 100755 index 9c38654a..00000000 --- a/test/clixon +++ /dev/null @@ -1,73 +0,0 @@ -#!/bin/sh -# Top-level cron scripts. Add this to (for example) /etc/cron.daily - -err(){ - testname=$1 - errcode=$2 - echo "Error in [$testname]" - logger "CLIXON: Error in [$testname]" - exit $errcode -} - -# cd to working dir -cd /var/tmp -if [ $# -ne 0 ]; then - err "usage: $0" 0 -fi -rm -rf cligen -rm -rf clixon -git clone https://github.com/olofhagsand/cligen.git -if [ $? -ne 0 ]; then - err "git clone cligen" 1 -fi -cd cligen -CFLAGS=-Werror ./configure -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -cd .. -git clone https://github.com/clicon/clixon.git -if [ $? -ne 0 ]; then - err "git clone clixon" 1 -fi -cd clixon -CFLAGS=-Werror ./configure --with-cligen=../cligen -if [ $? -ne 0 ]; then - err "configure" 2 -fi -make -if [ $? -ne 0 ]; then - err "make" 3 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install" 4 -fi -sudo make install-include -if [ $? -ne 0 ]; then - err "make install include" 5 - exit 1 -fi -cd example -make -if [ $? -ne 0 ]; then - err "make example" 6 -fi -sudo make install -if [ $? -ne 0 ]; then - err "make install example" 7 -fi -cd ../test -#./all.sh -(cd /home/olof/src/clixon/test; ./all.sh) -errcode=$? -if [ $errcode -ne 0 ]; then - err "test" $errcode -fi -cd ../.. -rm -rf clixon cligen -logger "CLIXON: tests OK" diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 61073308..d09c7812 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -128,7 +128,6 @@ new "restconf get empty config + state json" expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" new "restconf get empty config + state xml" -# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) expect="eth0eth42" match=`echo $ret | grep -EZo "$expect"` @@ -161,8 +160,19 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf Add subtree to datastore using POST" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' "" +new "restconf GET datastore" +expectfn "curl -s -X GET http://localhost/restconf/data" "data" + +new "restconf Add subtree to datastore using POST" +ret=$(curl -s -i -X POST -H "Accept: application/yang-data+json" -d '{"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}}' http://localhost/restconf/data) +expect="HTTP/1.1 200 OK" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + +new "restconf Re-add subtree which should give error" +expectfn 'curl -s -i -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"error-tag": "operation-failed"' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} @@ -182,7 +192,7 @@ expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface $' new "restconf Re-post eth/0/0 which should generate error" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "Data resource already exists" +expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' 'Object to create already exists' new "Add leaf description using POST" expectfn 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index cdd2d309..55a23308 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -81,10 +81,10 @@ new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" new "restconf POST again" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Data resource already exis" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Object to create already exists" new "restconf POST from top" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Data resource already exists" +expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Object to create already exists" new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" diff --git a/test/test_type.sh b/test/test_type.sh index dcc2288b..e63bd05a 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -4,8 +4,10 @@ # include err() and new() functions and creates $dir . ./lib.sh -fyang=$dir/type.yang + cfg=$dir/conf_yang.xml +fyang=$dir/type.yang + cat < $cfg @@ -70,6 +72,57 @@ module example{ enum down; } } + leaf length1 { + type string { + length "1"; + } + } +/* leaf length2 { + type string { + length "max"; + } + } + leaf length3 { + type string { + length "min"; + } + }*/ + leaf length4 { + type string { + length "4..4000"; + } + } +/* leaf length5 { + type string { + length "min..max"; + } + }*/ + leaf num1 { + type int32 { + range "1"; + } + } +/* leaf num2 { + type int32 { + range "min"; + } + } + leaf num3 { + type int32 { + range "max"; + } + } +*/ + leaf num4 { + type int32 { + range "4..4000"; + } + } +/* leaf num5 { + type int32 { + range "min..max"; + } + }*/ } EOF diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 3bcc0ce2..277c204d 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -304,7 +304,7 @@ module clixon-config { type boolean; default false; description "If set, modifications in validation and commit - callbacks will be saved into running"; + callbacks are written back into the datastore"; } } } From 96a3b586b8b2652fa3362b0ea3342bf023667575 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 11 Mar 2018 20:25:31 +0100 Subject: [PATCH 13/50] Restconf error handling --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee315073..80cdf30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Major changes: -* (Work in progress) Restconf error handling for get and edit operations +* (Work in progress) Restconf error handling for get, put and post. Several cornercases remain, including validate/commit. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). ### Minor changes: * Add username to rpc calls to prepare for authorization for backend: From 03be9c0567c6adfc9ba5619159cce251dd4f2fc4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 11 Mar 2018 22:49:10 +0100 Subject: [PATCH 14/50] Restconf error handling --- CHANGELOG.md | 2 +- apps/restconf/restconf_main.c | 43 +++++- apps/restconf/restconf_methods.c | 218 +++++++++++++++++-------------- apps/restconf/restconf_methods.h | 17 ++- test/test_restconf.sh | 2 +- test/test_restconf2.sh | 6 +- 6 files changed, 171 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80cdf30b..088e6482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Major changes: -* (Work in progress) Restconf error handling for get, put and post. Several cornercases remain, including validate/commit. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). +* Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). ### Minor changes: * Add username to rpc calls to prepare for authorization for backend: diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index a2be4db7..5d084403 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -104,24 +104,38 @@ api_data(clicon_handle h, { int retval = -1; char *request_method; + int pretty; + char *media_content_type; + int parse_xml = 0; /* By default expect and parse JSON */ + char *media_accept; + int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s", __FUNCTION__); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (media_content_type && + strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; + if (strcmp(request_method, "OPTIONS")==0) retval = api_data_options(h, r); else if (strcmp(request_method, "HEAD")==0) - retval = api_data_head(h, r, pcvec, pi, qvec, username); + retval = api_data_head(h, r, pcvec, pi, qvec, username, pretty, use_xml); else if (strcmp(request_method, "GET")==0) - retval = api_data_get(h, r, pcvec, pi, qvec, username); + retval = api_data_get(h, r, pcvec, pi, qvec, username, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, username); + retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, username, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PUT")==0) - retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, username); + retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, username, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PATCH")==0) retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, username); else if (strcmp(request_method, "DELETE")==0) - retval = api_data_delete(h, r, api_path, pi, username); + retval = api_data_delete(h, r, api_path, pi, username, pretty, use_xml); else retval = notfound(r); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -150,14 +164,29 @@ api_operations(clicon_handle h, { int retval = -1; char *request_method; + int pretty; + char *media_content_type; + int parse_xml = 0; /* By default expect and parse JSON */ + char *media_accept; + int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s", __FUNCTION__); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (media_content_type && + strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; + if (strcmp(request_method, "GET")==0) - retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username); + retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username, + pretty, use_xml, parse_xml); else retval = notfound(r); return retval; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 5ff69548..c5032c8b 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -211,6 +211,8 @@ api_return_err(clicon_handle h, * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * @param[in] head If 1 is HEAD, otherwise GET * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 @@ -235,6 +237,8 @@ api_data_get2(clicon_handle h, int pi, cvec *qvec, char *username, + int pretty, + int use_xml, int head) { int retval = -1; @@ -244,19 +248,12 @@ api_data_get2(clicon_handle h, yang_spec *yspec; cxobj *xret = NULL; cxobj *xerr; - char *media_accept; - int use_xml = 0; /* By default use JSON */ cxobj **xvec = NULL; size_t xlen; - int pretty; int i; cxobj *x; clicon_debug(1, "%s", __FUNCTION__); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; yspec = clicon_dbspec_yang(h); if ((cbpath = cbuf_new()) == NULL) goto done; @@ -349,9 +346,12 @@ api_data_get2(clicon_handle h, * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] username Authenticated user - The HEAD method is sent by the client to retrieve just the header fields - that would be returned for the comparable GET method, without the - response message-body. + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * + * The HEAD method is sent by the client to retrieve just the header fields + * that would be returned for the comparable GET method, without the + * response message-body. * Relation to netconf: none */ int @@ -360,9 +360,11 @@ api_data_head(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *username) + char *username, + int pretty, + int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, username, 1); + return api_data_get2(h, r, pcvec, pi, qvec, username, pretty, use_xml, 1); } /*! REST GET method @@ -373,6 +375,8 @@ api_data_head(clicon_handle h, * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode @@ -395,9 +399,11 @@ api_data_get(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *username) + char *username, + int pretty, + int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, username, 0); + return api_data_get2(h, r, pcvec, pi, qvec, username, pretty, use_xml, 0); } /*! Generic REST POST method @@ -409,6 +415,9 @@ api_data_get(clicon_handle h, * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * @note restconf POST is mapped to edit-config create. POST: @@ -436,11 +445,13 @@ api_data_post(clicon_handle h, int pi, cvec *qvec, char *data, - char *username) + char *username, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; enum operation_type op = OP_CREATE; - int pretty; int i; cxobj *xdata = NULL; cbuf *cbx = NULL; @@ -451,24 +462,13 @@ api_data_post(clicon_handle h, yang_spec *yspec; cxobj *xa; cxobj *xu; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ cxobj *xret = NULL; + cxobj *xretcom = NULL; cxobj *xerr; - char *media_accept; - int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path, data); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -527,7 +527,6 @@ api_data_post(clicon_handle h, cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; - cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) @@ -535,14 +534,17 @@ api_data_post(clicon_handle h, if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; - goto done; + goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ + if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) + goto done; + if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) goto done; - badrequest(r); - goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -553,6 +555,8 @@ api_data_post(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xret) xml_free(xret); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); if (xdata) @@ -623,6 +627,10 @@ match_list_keys(yang_stmt *y, * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data + * @note restconf PUT is mapped to edit-config replace. * @example curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 @@ -642,7 +650,10 @@ api_data_put(clicon_handle h, int pi, cvec *qvec, char *data, - char *username) + char *username, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; enum operation_type op = OP_REPLACE; @@ -657,16 +668,13 @@ api_data_put(clicon_handle h, yang_spec *yspec; cxobj *xa; cxobj *xu; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ char *api_path; + cxobj *xret = NULL; + cxobj *xretcom = NULL; + cxobj *xerr; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -746,21 +754,27 @@ api_data_put(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; + cprintf(cbx, ""); clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - notfound(r); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } - /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ - if (clicon_rpc_discard_changes(h) < 0) - goto done; - badrequest(r); + if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) goto done; + if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if (clicon_rpc_discard_changes(h) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); /* Created */ FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -769,6 +783,10 @@ api_data_put(clicon_handle h, retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (xret) + xml_free(xret); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); if (xdata) @@ -807,8 +825,10 @@ api_data_patch(clicon_handle h, * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) - * @param[in] username Authenticated user * @param[in] pi Offset, where path starts + * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 * Netconf: (nc:operation="delete") @@ -818,7 +838,9 @@ api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, - char *username) + char *username, + int pretty, + int use_xml) { int retval = -1; int i; @@ -830,6 +852,9 @@ api_data_delete(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; enum operation_type op = OP_DELETE; + cxobj *xret = NULL; + cxobj *xretcom = NULL; + cxobj *xerr; clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -860,21 +885,27 @@ api_data_delete(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; - + cprintf(cbx, ""); + cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; - if (clicon_rpc_edit_config(h, "candidate", - OP_NONE, - cbuf_get(cbx)) < 0){ - notfound(r); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_commit(h) < 0){ - if (clicon_rpc_discard_changes(h) < 0) - goto done; - badrequest(r); + if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) goto done; + if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if (clicon_rpc_discard_changes(h) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; } FCGX_SetExitStatus(201, r->out); FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n"); @@ -884,6 +915,10 @@ api_data_delete(clicon_handle h, done: if (cbx) cbuf_free(cbx); + if (xret) + xml_free(xret); + if (xretcom) + xml_free(xretcom); if (xtop) xml_free(xtop); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -899,6 +934,8 @@ api_data_delete(clicon_handle h, * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML * * @code * curl -G http://localhost/restconf/operations @@ -911,18 +948,17 @@ api_data_delete(clicon_handle h, */ int api_operations_get(clicon_handle h, - FCGX_Request *r, - char *path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data, - char *username) + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + char *username, + int pretty, + int use_xml) { int retval = -1; - int pretty; - char *media_accept; - int use_xml = 0; /* By default use JSON */ yang_spec *yspec; yang_stmt *ym; yang_stmt *yc; @@ -932,10 +968,6 @@ api_operations_get(clicon_handle h, cxobj *xt = NULL; clicon_debug(1, "%s", __FUNCTION__); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; yspec = clicon_dbspec_yang(h); if ((cbx = cbuf_new()) == NULL) goto done; @@ -994,19 +1026,25 @@ api_operations_get(clicon_handle h, * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data * @param[in] username Authenticated user + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * @note We map post to edit-config create. POST {+restconf}/operations/ */ int api_operations_post(clicon_handle h, - FCGX_Request *r, - char *path, - cvec *pcvec, - int pi, - cvec *qvec, - char *data, - char *username) + FCGX_Request *r, + char *path, + cvec *pcvec, + int pi, + cvec *qvec, + char *data, + char *username, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; int i; @@ -1024,24 +1062,9 @@ api_operations_post(clicon_handle h, cxobj *xinput; cxobj *xoutput; cxobj *x; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ - char *media_accept; - int use_xml = 0; /* By default return JSON */ - int pretty; cxobj *xa; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - if ((media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp)) && - strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; - clicon_debug(1, "%s accept:\"%s\" content-type:\"%s\"", - __FUNCTION__, media_accept, media_content_type); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -1128,8 +1151,7 @@ api_operations_post(clicon_handle h, xml_name_set(xoutput, "output"); if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL && xoutput){ - - clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); + // clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index 246552b2..b482d9ce 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -46,28 +46,31 @@ */ int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec, char *username); + cvec *qvec, char *username, int pretty, int use_xml); int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec, char *username); + cvec *qvec, char *username, int pretty, int use_xml); int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data, char *username); + cvec *qvec, char *data, char *username, + int pretty, int use_xml, int parse_xml); int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data, char *username); + cvec *qvec, char *data, char *username, + int pretty, int use_xml, int parse_xml); int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, cvec *qvec, char *data, char *username); int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, - char *username); + char *username, int pretty, int use_xml); int api_operations_get(clicon_handle h, FCGX_Request *r, char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, char *username); + cvec *pcvec, int pi, cvec *qvec, char *data, char *username, + int pretty, int use_xml); int api_operations_post(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, int pi, cvec *qvec, char *data, - char *username); + char *username, int pretty, int use_xml, int parse_xml); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/test/test_restconf.sh b/test/test_restconf.sh index d09c7812..d0515410 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -211,7 +211,7 @@ new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' $state new "restconf Re-Delete eth/0/0 using none should generate error" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "Not Found" +expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '"error-message": "Object to delete does not exist"' new "restconf Add subtree eth/0/0 using PUT" expectfn 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 55a23308..db275dc1 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -77,6 +77,9 @@ expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0" new "restconf GET if-type" expectfn "curl -s -X GET http://localhost/restconf/data/cont1/interface=local0/type" '{"type": "regular"}' +new "restconf POST interface without mandatory type" +expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/restconf/data/cont1' '"error-message": "Missing mandatory variable: type"' + new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" @@ -133,9 +136,6 @@ expectfn 'curl -s -X PUT -d {"interface":{"name":"TEST","type":"eth0"}} http://l 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" -new "restconf POST invalid no type" -expectfn 'curl -s -X POST -d {"interface":{"name":"ALPHA"}} http://localhost/restconf/data/cont1' "Bad request" - new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf From 9b951b5ce604207afc0e93bd6be00fa7afe386ee Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 13 Mar 2018 23:17:36 +0000 Subject: [PATCH 15/50] restconf json err return: too many {} --- apps/restconf/restconf_methods.c | 17 ++++++++++++----- test/test_restconf.sh | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index c5032c8b..8cadab90 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -188,11 +188,18 @@ api_return_err(clicon_handle h, FCGX_FPrintF(r->out, " \r\n"); } else{ - FCGX_FPrintF(r->out, "{%s", pretty?"\r\n":""); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {%s", pretty?"\r\n":""); - FCGX_FPrintF(r->out, " %s", cbuf_get(cb)); - FCGX_FPrintF(r->out, " }%s", pretty?"\r\n":""); - FCGX_FPrintF(r->out, "}\r\n"); + if (pretty){ + FCGX_FPrintF(r->out, "{\r\n"); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\r\n", + cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } + else{ + FCGX_FPrintF(r->out, "{"); + FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } } ok: retval = 0; diff --git a/test/test_restconf.sh b/test/test_restconf.sh index d0515410..99920060 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -172,7 +172,7 @@ if [ -z "$match" ]; then fi new "restconf Re-add subtree which should give error" -expectfn 'curl -s -i -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"error-tag": "operation-failed"' +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Object to create already exists"}}}' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} From 52e510cfdf8860ace3a9c147bdba33741510460c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 18 Mar 2018 16:57:18 +0000 Subject: [PATCH 16/50] The key-value datastore is no longer supported. Use the default text datastore. --- configure | 109 --------------------------- configure.ac | 21 ------ datastore/Makefile.in | 3 - include/clixon_config.h.in | 9 --- include/clixon_config.h.in~ | 142 ++++++++++++++++++++++++++++++++++++ include/clixon_config.h~ | 134 ++++++++++++++++++++++++++++++++++ 6 files changed, 276 insertions(+), 142 deletions(-) create mode 100644 include/clixon_config.h.in~ create mode 100644 include/clixon_config.h~ diff --git a/configure b/configure index 44813e8a..aaf3caff 100755 --- a/configure +++ b/configure @@ -632,7 +632,6 @@ CPP OBJEXT EXEEXT ac_ct_CC -with_keyvalue with_restconf RANLIB AR @@ -707,8 +706,6 @@ ac_user_opts=' enable_option_checking with_cligen with_restconf -with_keyvalue -with_qdbm with_configfile ' ac_precious_vars='build_alias @@ -1346,8 +1343,6 @@ Optional Packages: --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-cligen=dir Use CLIGEN here --without-restconf disable support for restconf - --with-keyvalue enable support for key-value xmldb datastore - --with-qdbm=dir Use QDBM here, if keyvalue --with-configfile=FILE set default path to config file Some influential environment variables: @@ -2349,7 +2344,6 @@ test -n "$target_alias" && # If yes, compile apps/restconf - # If yes, compile datastore/keyvalue # ac_ext=c ac_cpp='$CPP $CPPFLAGS' @@ -3946,107 +3940,6 @@ fi fi -# This is for keyvalue datastore (and qdbm) - -# Check whether --with-keyvalue was given. -if test "${with_keyvalue+set}" = set; then : - withval=$with_keyvalue; -else - with_keyvalue=no -fi - -if test "x${with_keyvalue}" == xyes; then - # This is for qdbm - -# Check whether --with-qdbm was given. -if test "${with_qdbm+set}" = set; then : - withval=$with_qdbm; -fi - - if test "${with_qdbm}"; then - echo "Using QDBM here: ${with_qdbm}" - CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" - LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" - fi - # Problem: depot.h may be in qdbm/depot.h. - for ac_header in depot.h -do : - ac_fn_c_check_header_mongrel "$LINENO" "depot.h" "ac_cv_header_depot_h" "$ac_includes_default" -if test "x$ac_cv_header_depot_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_DEPOT_H 1 -_ACEOF - -else - for ac_header in qdbm/depot.h -do : - ac_fn_c_check_header_mongrel "$LINENO" "qdbm/depot.h" "ac_cv_header_qdbm_depot_h" "$ac_includes_default" -if test "x$ac_cv_header_qdbm_depot_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_QDBM_DEPOT_H 1 -_ACEOF - -else - as_fn_error $? "libqdbm-dev required" "$LINENO" 5 -fi - -done - -fi - -done - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dpopen in -lqdbm" >&5 -$as_echo_n "checking for dpopen in -lqdbm... " >&6; } -if ${ac_cv_lib_qdbm_dpopen+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-lqdbm $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char dpopen (); -int -main () -{ -return dpopen (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_qdbm_dpopen=yes -else - ac_cv_lib_qdbm_dpopen=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_qdbm_dpopen" >&5 -$as_echo "$ac_cv_lib_qdbm_dpopen" >&6; } -if test "x$ac_cv_lib_qdbm_dpopen" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_LIBQDBM 1 -_ACEOF - - LIBS="-lqdbm $LIBS" - -else - as_fn_error $? "libqdbm-dev required" "$LINENO" 5 -fi - - ac_config_files="$ac_config_files datastore/keyvalue/Makefile" - -fi - # Set default config file location # Check whether --with-configfile was given. @@ -4311,7 +4204,6 @@ _ACEOF -# See also datastore/keyvalue/Makefile in with_keyvalue clause above ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile extras/rpm/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile datastore/Makefile datastore/text/Makefile yang/Makefile doc/Makefile" cat >confcache <<\_ACEOF @@ -5005,7 +4897,6 @@ do case $ac_config_target in "include/clixon_config.h") CONFIG_HEADERS="$CONFIG_HEADERS include/clixon_config.h" ;; "lib/clixon/clixon.h") CONFIG_HEADERS="$CONFIG_HEADERS lib/clixon/clixon.h" ;; - "datastore/keyvalue/Makefile") CONFIG_FILES="$CONFIG_FILES datastore/keyvalue/Makefile" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "lib/Makefile") CONFIG_FILES="$CONFIG_FILES lib/Makefile" ;; "lib/src/Makefile") CONFIG_FILES="$CONFIG_FILES lib/src/Makefile" ;; diff --git a/configure.ac b/configure.ac index b0bbed2f..22614971 100644 --- a/configure.ac +++ b/configure.ac @@ -85,7 +85,6 @@ AC_SUBST(EXE_SUFFIX) AC_SUBST(AR) AC_SUBST(RANLIB) AC_SUBST(with_restconf) # If yes, compile apps/restconf -AC_SUBST(with_keyvalue) # If yes, compile datastore/keyvalue # AC_PROG_CC() AC_PROG_CPP @@ -142,25 +141,6 @@ if test "x${with_restconf}" == xyes; then AC_CHECK_LIB(fcgi, FCGX_Init,, AC_MSG_ERROR([libfcgi-dev missing])) fi -# This is for keyvalue datastore (and qdbm) -AC_ARG_WITH([keyvalue], - [AS_HELP_STRING([--with-keyvalue],[enable support for key-value xmldb datastore])], - [], - [with_keyvalue=no]) -if test "x${with_keyvalue}" == xyes; then - # This is for qdbm - AC_ARG_WITH(qdbm, [ --with-qdbm=dir Use QDBM here, if keyvalue ] ) - if test "${with_qdbm}"; then - echo "Using QDBM here: ${with_qdbm}" - CPPFLAGS="-I${with_qdbm}/include ${CPPFLAGS}" - LDFLAGS="-L${with_qdbm}/lib ${LDFLAGS}" - fi - # Problem: depot.h may be in qdbm/depot.h. - AC_CHECK_HEADERS(depot.h,,[AC_CHECK_HEADERS(qdbm/depot.h,,AC_MSG_ERROR(libqdbm-dev required))]) - AC_CHECK_LIB(qdbm, dpopen,, AC_MSG_ERROR(libqdbm-dev required)) - AC_CONFIG_FILES(datastore/keyvalue/Makefile) -fi - # Set default config file location AC_ARG_WITH([configfile], [AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])], @@ -193,7 +173,6 @@ AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${DEFAULT_CONFIG}",[Location for apps AH_BOTTOM([#include ]) -# See also datastore/keyvalue/Makefile in with_keyvalue clause above AC_OUTPUT(Makefile lib/Makefile lib/src/Makefile diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 6e5ce1fe..697bce7c 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -68,9 +68,6 @@ INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/inclu SUBDIRS = text -ifeq ($(with_keyvalue),yes) -SUBDIRS += keyvalue -endif .PHONY: all clean depend install $(SUBDIRS) diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index d356c165..5b0cb825 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -27,9 +27,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CRYPT_H -/* Define to 1 if you have the header file. */ -#undef HAVE_DEPOT_H - /* Define to 1 if you have the `inet_aton' function. */ #undef HAVE_INET_ATON @@ -54,9 +51,6 @@ /* Define to 1 if you have the `nsl' library (-lnsl). */ #undef HAVE_LIBNSL -/* Define to 1 if you have the `qdbm' library (-lqdbm). */ -#undef HAVE_LIBQDBM - /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET @@ -66,9 +60,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H -/* Define to 1 if you have the header file. */ -#undef HAVE_QDBM_DEPOT_H - /* Define to 1 if you have the `sigaction' function. */ #undef HAVE_SIGACTION diff --git a/include/clixon_config.h.in~ b/include/clixon_config.h.in~ new file mode 100644 index 00000000..d356c165 --- /dev/null +++ b/include/clixon_config.h.in~ @@ -0,0 +1,142 @@ +/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ + +/* Clixon data dir for system yang files etc */ +#undef CLIXON_DATADIR + +/* Location for apps to find default config file */ +#undef CLIXON_DEFAULT_CONFIG + +/* Clixon major release */ +#undef CLIXON_VERSION_MAJOR + +/* Clixon minor release */ +#undef CLIXON_VERSION_MINOR + +/* Clixon path version */ +#undef CLIXON_VERSION_PATCH + +/* Clixon version string */ +#undef CLIXON_VERSION_STRING + +/* Define to 1 if you have the `alphasort' function. */ +#undef HAVE_ALPHASORT + +/* Define to 1 if you have the header file. */ +#undef HAVE_CLIGEN_CLIGEN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_CRYPT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_DEPOT_H + +/* Define to 1 if you have the `inet_aton' function. */ +#undef HAVE_INET_ATON + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the `cligen' library (-lcligen). */ +#undef HAVE_LIBCLIGEN + +/* Define to 1 if you have the `crypt' library (-lcrypt). */ +#undef HAVE_LIBCRYPT + +/* Define to 1 if you have the `dl' library (-ldl). */ +#undef HAVE_LIBDL + +/* Define to 1 if you have the `fcgi' library (-lfcgi). */ +#undef HAVE_LIBFCGI + +/* Define to 1 if you have the `m' library (-lm). */ +#undef HAVE_LIBM + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#undef HAVE_LIBNSL + +/* Define to 1 if you have the `qdbm' library (-lqdbm). */ +#undef HAVE_LIBQDBM + +/* Define to 1 if you have the `socket' library (-lsocket). */ +#undef HAVE_LIBSOCKET + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_IF_VLAN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_QDBM_DEPOT_H + +/* Define to 1 if you have the `sigaction' function. */ +#undef HAVE_SIGACTION + +/* Define to 1 if you have the `sigvec' function. */ +#undef HAVE_SIGVEC + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `strlcpy' function. */ +#undef HAVE_STRLCPY + +/* Define to 1 if you have the `strndup' function. */ +#undef HAVE_STRNDUP + +/* Define to 1 if you have the `strsep' function. */ +#undef HAVE_STRSEP + +/* Define to 1 if you have the `strverscmp' function. */ +#undef HAVE_STRVERSCMP + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UCRED_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the `versionsort' function. */ +#undef HAVE_VERSIONSORT + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +#undef YYTEXT_POINTER + +#include diff --git a/include/clixon_config.h~ b/include/clixon_config.h~ new file mode 100644 index 00000000..5be37864 --- /dev/null +++ b/include/clixon_config.h~ @@ -0,0 +1,134 @@ +/* include/clixon_config.h. Generated from clixon_config.h.in by configure. */ +/* include/clixon_config.h.in. Generated from configure.ac by autoheader. */ + +/* Clixon major release */ +#define CLIXON_VERSION_MAJOR 3 + +/* Clixon minor release */ +#define CLIXON_VERSION_MINOR 3 + +/* Clixon path version */ +#define CLIXON_VERSION_PATCH 2 + +/* Clixon version string */ +#define CLIXON_VERSION_STRING "3.3.2" + +/* Define to 1 if you have the `alphasort' function. */ +#define HAVE_ALPHASORT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CLIGEN_CLIGEN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CRYPT_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DEPOT_H */ + +/* Define to 1 if you have the `inet_aton' function. */ +#define HAVE_INET_ATON 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `crypt' library (-lcrypt). */ +#define HAVE_LIBCRYPT 1 + +/* Define to 1 if you have the `dl' library (-ldl). */ +#define HAVE_LIBDL 1 + +/* Define to 1 if you have the `fcgi' library (-lfcgi). */ +#define HAVE_LIBFCGI 1 + +/* Define to 1 if you have the `m' library (-lm). */ +#define HAVE_LIBM 1 + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +#define HAVE_LIBNSL 1 + +/* Define to 1 if you have the `qdbm' library (-lqdbm). */ +#define HAVE_LIBQDBM 1 + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LINUX_IF_VLAN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_QDBM_DEPOT_H 1 + +/* Define to 1 if you have the `sigaction' function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the `sigvec' function. */ +#define HAVE_SIGVEC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strlcpy' function. */ +/* #undef HAVE_STRLCPY */ + +/* Define to 1 if you have the `strndup' function. */ +#define HAVE_STRNDUP 1 + +/* Define to 1 if you have the `strsep' function. */ +#define HAVE_STRSEP 1 + +/* Define to 1 if you have the `strverscmp' function. */ +#define HAVE_STRVERSCMP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UCRED_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `versionsort' function. */ +#define HAVE_VERSIONSORT 1 + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a + `char[]'. */ +#define YYTEXT_POINTER 1 + +#include From efa72e9e6f6b50faf74bbd64dcd7f3003eb90eff Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 18 Mar 2018 18:06:02 +0000 Subject: [PATCH 17/50] * Proper RFC 6241 Netconf error handling * New functions added in clixon_netconf_lib.[ch] * Datastore code modified for RFC 6241 * Remaining: validate, generic restconf and netconf code --- CHANGELOG.md | 6 + apps/backend/backend_client.c | 439 +++++++----------- apps/backend/backend_commit.c | 79 ++-- apps/backend/backend_main.c | 62 ++- apps/netconf/netconf_main.c | 10 +- apps/restconf/restconf_lib.c | 3 +- apps/restconf/restconf_main.c | 18 +- apps/restconf/restconf_methods.c | 36 +- datastore/datastore_client.c | 7 +- datastore/text/clixon_xmldb_text.c | 70 ++- datastore/text/clixon_xmldb_text.h | 2 +- example/routing_backend.c | 3 +- example/routing_cli.c | 2 +- lib/clixon/clixon_netconf_lib.h | 63 +++ lib/clixon/clixon_xml.h | 5 +- lib/clixon/clixon_xml_db.h | 4 +- lib/src/Makefile.in | 2 +- lib/src/clixon_netconf_lib.c | 698 +++++++++++++++++++++++++++++ lib/src/clixon_xml.c | 10 +- lib/src/clixon_xml_db.c | 18 +- test/lib.sh | 41 +- test/test_datastore.sh | 2 +- test/test_leafref.sh | 6 +- test/test_netconf.sh | 2 +- test/test_restconf.sh | 79 ++-- test/test_restconf2.sh | 4 +- 26 files changed, 1196 insertions(+), 475 deletions(-) create mode 100644 lib/clixon/clixon_netconf_lib.h create mode 100644 lib/src/clixon_netconf_lib.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 088e6482..80851261 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,14 @@ ### Major changes: * Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). +* Proper RFC 6241 Netconf error handling + * New functions added in clixon_netconf_lib.[ch] + * Datastore code modified for RFC 6241 + * Remaining: validate, generic restconf and netconf code ### Minor changes: + +* The key-value datastore is no longer supported. Use the default text datastore. * Add username to rpc calls to prepare for authorization for backend: clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 97b0ae79..a6ea354f 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -180,7 +180,7 @@ backend_client_rm(clicon_handle h, return backend_client_delete(h, ce); /* actually purge it */ } -/*! FInd target/source in netconf request. Assume sanity made so not finding is error */ +/*! Find target/source in netconf request. Assume sanity- not finding is error */ static char* netconf_db_find(cxobj *xn, char *name) @@ -214,31 +214,28 @@ from_client_get_config(clicon_handle h, cxobj *xfilter; char *selector = "/"; cxobj *xret = NULL; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "source")) == NULL){ clicon_err(OE_XML, 0, "db not found"); goto done; } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - if ((xfilter = xml_find(xe, "filter")) != NULL) if ((selector = xml_find_value(xfilter, "select"))==NULL) selector="/"; if (xmldb_get(h, db, selector, 1, &xret) < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", "read registry")< 0) + goto done; goto ok; } cprintf(cbret, ""); @@ -254,6 +251,8 @@ from_client_get_config(clicon_handle h, ok: retval = 0; done: + if (cbx) + cbuf_free(cbx); if (xret) xml_free(xret); return retval; @@ -276,18 +275,15 @@ from_client_get(clicon_handle h, char *selector = "/"; cxobj *xret = NULL; int ret; + cbuf *cbx = NULL; /* Assist cbuf */ if ((xfilter = xml_find(xe, "filter")) != NULL) if ((selector = xml_find_value(xfilter, "select"))==NULL) selector="/"; /* Get config */ if (xmldb_get(h, "running", selector, 0, &xret) < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", "read registry")< 0) + goto done; goto ok; } /* Get state data from plugins as defined by plugin_statedata(), if any */ @@ -308,23 +304,25 @@ from_client_get(clicon_handle h, cprintf(cbret, ""); } else { /* 1 Error from callback */ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error:%s" - "", clicon_err_reason); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "Internal error:%s", clicon_err_reason); + if (netconf_operation_failed(cbret, "rpc", cbuf_get(cbx))< 0) + goto done; clicon_log(LOG_NOTICE, "%s Error in backend_statedata_call:%s", __FUNCTION__, xml_name(xe)); } ok: retval = 0; done: + if (cbx) + cbuf_free(cbx); if (xret) xml_free(xret); return retval; } - /*! Internal message: edit-config * * @param[in] h Clicon handle @@ -340,14 +338,13 @@ from_client_edit_config(clicon_handle h, { int retval = -1; char *target; - cbuf *cb = NULL; - cxobj *xret = NULL; cxobj *xc; cxobj *x; enum operation_type operation = OP_MERGE; int piddb; int non_config = 0; yang_spec *yspec; + cbuf *cbx = NULL; /* Assist cbuf */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); @@ -357,50 +354,44 @@ from_client_edit_config(clicon_handle h, clicon_err(OE_XML, 0, "db not found"); goto done; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if ((x = xpath_first(xn, "default-operation")) != NULL){ if (xml_operation(xml_body(x), &operation) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - ""); + if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0) + goto done; goto ok; } } - if ((xc = xpath_first(xn, "config")) != NULL){ + if ((xc = xpath_first(xn, "config")) == NULL){ + if (netconf_missing_element(cbret, "protocol", "config", NULL) < 0) + goto done; + goto ok; + } + else{ if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0) goto done; if (non_config){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "state data not allowed" - ""); + if (netconf_invalid_value(cbret, "protocol", "State data not allowed")< 0) + goto done; goto ok; } /* Cant do this earlier since we dont have a yang spec to @@ -408,35 +399,23 @@ from_client_edit_config(clicon_handle h, */ if (xml_child_sort && xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0) goto done; - if (xmldb_put(h, target, operation, xc) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "%s" - "", clicon_err_reason); + if (xmldb_put(h, target, operation, xc, cbret) < 0){ + clicon_debug(1, "%s ERROR PUT", __FUNCTION__); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } } - else{ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "config" - ""); - goto ok; - } - cprintf(cbret, ""); ok: + if (!cbuf_len(cbret)) + cprintf(cbret, ""); retval = 0; done: - if (xret) - xml_free(xret); - if (cb) - cbuf_free(cb); + if (cbx) + cbuf_free(cbx); + clicon_debug(1, "%s done cbret:%s", __FUNCTION__, cbuf_get(cbret)); return retval; -} +} /* from_client_edit_config */ /*! Internal message: Lock database * @@ -454,26 +433,23 @@ from_client_lock(clicon_handle h, int retval = -1; char *db; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; goto ok; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - /* * A lock MUST not be granted if either of the following conditions is true: * 1) A lock is already held by any NETCONF session or another entity. @@ -482,23 +458,21 @@ from_client_lock(clicon_handle h, */ piddb = xmldb_islocked(h, db); if (piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Lock failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } else{ - xmldb_lock(h, db, pid); + if (xmldb_lock(h, db, pid) < 0) + goto done; cprintf(cbret, ""); } ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -518,23 +492,21 @@ from_client_unlock(clicon_handle h, int retval = -1; char *db; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((db = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; goto ok; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(db) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", db); + cprintf(cbx, "No such database: %s", db); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } piddb = xmldb_islocked(h, db); @@ -546,14 +518,9 @@ from_client_unlock(clicon_handle h, * session that obtained the lock */ if (piddb==0 || piddb != pid){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Unlock failed, lock is already held" - "pid=%d piddb=%d" - "", - pid, piddb); + cprintf(cbx, "pid=%d piddb=%d", pid, piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Unlock failed, lock is already held") < 0) + goto done; goto ok; } else{ @@ -585,15 +552,11 @@ from_client_kill_session(clicon_handle h, struct client_entry *ce; char *db = "running"; /* XXX */ cxobj *x; - + if ((x = xml_find(xe, "session-id")) == NULL || (str = xml_find_value(x, "body")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "session-id" - ""); + if (netconf_missing_element(cbret, "protocol", "session-id", NULL) < 0) + goto done; goto ok; } pid = atoi(str); @@ -618,18 +581,14 @@ from_client_kill_session(clicon_handle h, xmldb_unlock(h, db); } else{ /* failed to kill client */ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "Faile to kill session" - ""); + if (netconf_operation_failed(cbret, "application", "Failed to kill session")< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: return retval; } @@ -647,74 +606,57 @@ from_client_copy_config(clicon_handle h, int mypid, cbuf *cbret) { - char *source; - char *target; - int retval = -1; - int piddb; - + char *source; + char *target; + int retval = -1; + int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ + if ((source = netconf_db_find(xe, "source")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "source" - ""); + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; goto ok; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(source) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", source); + cprintf(cbx, "No such database: %s", source); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - if ((target = netconf_db_find(xe, "target")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; goto ok; } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Copy failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_copy(h, source, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -732,67 +674,51 @@ from_client_delete_config(clicon_handle h, int mypid, cbuf *cbret) { - int retval = -1; - char *target; - int piddb; + int retval = -1; + char *target; + int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ if ((target = netconf_db_find(xe, "target")) == NULL|| strcmp(target, "running")==0){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "target" - ""); + if (netconf_missing_element(cbret, "protocol", "target", NULL) < 0) + goto done; goto ok; } + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } if (xmldb_validate_db(target) < 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "No such database: %s" - "", target); + cprintf(cbx, "No such database: %s", target); + if (netconf_invalid_value(cbret, "protocol", cbuf_get(cbx))< 0) + goto done; goto ok; } - /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_delete(h, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "Internal error" - "%s" - "", clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } if (xmldb_create(h, target) < 0){ - cprintf(cbret, "" - "operation-failed" - "protocol" - "error" - "Internal error" - "%s" - "", clicon_err_reason); + if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; } @@ -829,13 +755,8 @@ from_client_create_subscription(clicon_handle h, if ((ftype = xml_find_value(x, "type")) != NULL){ /* Only accept xpath as filter type */ if (strcmp(ftype, "xpath") != 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "only xpath filter type supported" - "type" - ""); + if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0) + goto done; goto ok; } } @@ -866,12 +787,8 @@ from_client_debug(clicon_handle h, char *valstr; if ((valstr = xml_find_body(xe, "level")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "level" - ""); + if (netconf_missing_element(cbret, "application", "level", NULL) < 0) + goto done; goto ok; } level = atoi(valstr); @@ -882,7 +799,7 @@ from_client_debug(clicon_handle h, cprintf(cbret, ""); ok: retval = 0; - //done: + done: return retval; } @@ -917,23 +834,13 @@ from_client_msg(clicon_handle h, goto done; } if (clicon_msg_decode(msg, &xt) < 0){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "rpc expected" - "Not recognized" - ""); + if (netconf_malformed_message(cbret, "Not recognized, rpc expected")< 0) + goto done; goto reply; } if ((x = xpath_first(xt, "/rpc")) == NULL){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "rpc expected" - "Not recognized" - ""); + if (netconf_malformed_message(cbret, "Not recognized, rpc expected")< 0) + goto done; goto reply; } xe = NULL; @@ -977,12 +884,8 @@ from_client_msg(clicon_handle h, } else if (strcmp(name, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ - cprintf(cbret, "" - "missing-element" - "protocol" - "error" - "source" - ""); + if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) + goto done; goto reply; } if (from_client_validate(h, db, cbret) < 0) @@ -1007,34 +910,22 @@ from_client_msg(clicon_handle h, else{ clicon_err_reset(); if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error:%s" - "", clicon_err_reason); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; clicon_log(LOG_NOTICE, "%s Error in backend_rpc_call:%s", __FUNCTION__, xml_name(xe)); goto reply; /* Dont quit here on user callbacks */ } - if (ret == 0) /* not handled by callback */ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "%s" - "Not recognized" - "", - name); + if (ret == 0){ /* not handled by callback */ + if (netconf_operation_failed(cbret, "application", "Callback not recognized")< 0) + goto done; + goto reply; + } } } reply: if (cbuf_len(cbret) == 0) - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "Internal error %s" - "",clicon_err_reason); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){ switch (errno){ @@ -1055,7 +946,7 @@ from_client_msg(clicon_handle h, } // ok: retval = 0; - done: + done: if (xt) xml_free(xt); if (cbret) diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index ef73adf2..6df3852f 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -241,7 +241,7 @@ candidate_commit(clicon_handle h, /* Optionally write (potentially modified) tree back to candidate */ if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, candidate, OP_REPLACE, td->td_target) < 0) + if (xmldb_put(h, candidate, OP_REPLACE, td->td_target, NULL) < 0) goto done; /* 8. Success: Copy candidate to running */ @@ -282,35 +282,32 @@ from_client_commit(clicon_handle h, { int retval = -1; int piddb; + cbuf *cbx = NULL; /* Assist cbuf */ /* Check if target locked by other client */ piddb = xmldb_islocked(h, "running"); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (candidate_commit(h, "candidate") < 0){ /* Assume validation fail, nofatal */ clicon_debug(1, "Commit candidate failed"); - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - "%s" - "", - clicon_err_reason); + if (netconf_invalid_value(cbret, "protocol", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } /* from_client_commit */ @@ -328,33 +325,31 @@ from_client_discard_changes(clicon_handle h, { int retval = -1; int piddb; - + cbuf *cbx = NULL; /* Assist cbuf */ + /* Check if target locked by other client */ piddb = xmldb_islocked(h, "candidate"); if (piddb && mypid != piddb){ - cprintf(cbret, "" - "lock-denied" - "protocol" - "error" - "Operation failed, lock is already held" - "%d" - "", - piddb); + if ((cbx = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cprintf(cbx, "%d", piddb); + if (netconf_lock_denied(cbret, cbuf_get(cbx), "Operation failed, lock is already held") < 0) + goto done; goto ok; } if (xmldb_copy(h, "running", "candidate") < 0){ - cprintf(cbret, "" - "operation-failed" - "application" - "error" - "read-registry" - ""); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } cprintf(cbret, ""); ok: retval = 0; - // done: + done: + if (cbx) + cbuf_free(cbx); return retval; /* may be zero if we ignoring errors from commit */ } @@ -374,11 +369,8 @@ from_client_validate(clicon_handle h, transaction_data_t *td = NULL; if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){ - cprintf(cbret, "" - "invalid-value" - "protocol" - "error" - ""); + if (netconf_invalid_value(cbret, "protocol", "No such database")< 0) + goto done; goto ok; } clicon_debug(1, "Validate %s", db); @@ -390,18 +382,13 @@ from_client_validate(clicon_handle h, if (validate_common(h, db, td) < 0){ clicon_debug(1, "Validate %s failed", db); /* XXX: candidate_validate should have proper error handling */ - cprintf(cbret, "" - "missing-attribute" - "protocol" - "error" - "%s" - "", - clicon_err_reason); + if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) + goto done; goto ok; } /* Optionally write (potentially modified) tree back to candidate */ if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")) - if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target) < 0) + if (xmldb_put(h, "candidate", OP_REPLACE, td->td_target, NULL) < 0) goto done; cprintf(cbret, ""); ok: diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 31cb39c2..c8eb25eb 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -174,8 +174,8 @@ db_merge(clicon_handle h, /* Get data as xml from db1 */ if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0) goto done; - /* Merge xml into db2. WIthout commit */ - if (xmldb_put(h, (char*)db2, OP_MERGE, xt) < 0) + /* Merge xml into db2. Without commit */ + if (xmldb_put(h, (char*)db2, OP_MERGE, xt, NULL) < 0) goto done; retval = 0; done: @@ -283,7 +283,7 @@ load_extraxml(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; /* Merge user reset state */ - if (xmldb_put(h, (char*)db, OP_MERGE, xt) < 0) + if (xmldb_put(h, (char*)db, OP_MERGE, xt, NULL) < 0) goto done; retval = 0; done: @@ -703,43 +703,41 @@ main(int argc, char **argv) if ((xml_pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) >= 0) if (xmldb_setopt(h, "pretty", (void*)(intptr_t)xml_pretty) < 0) goto done; - /* If startup mode is not defined, eg via OPTION or -s, assume old method */ + /* Startup mode needs to be defined, */ startup_mode = clicon_startup_mode(h); if (startup_mode == -1){ clicon_log(LOG_ERR, "Startup mode undefined. Specify option CLICON_STARTUP_MODE or specify -s option to clicon_backend.\n"); goto done; } - else { - /* Init running db if it is not there - */ - if (xmldb_exists(h, "running") != 1) - if (xmldb_create(h, "running") < 0) - return -1; - switch (startup_mode){ - case SM_NONE: - if (startup_mode_none(h) < 0) - goto done; - break; - case SM_INIT: /* -I */ - if (startup_mode_init(h) < 0) - goto done; - break; - case SM_RUNNING: /* -CIr */ - if (startup_mode_running(h, extraxml_file) < 0) - goto done; - break; - case SM_STARTUP: /* startup configuration */ - if (startup_mode_startup(h, extraxml_file) < 0) - goto done; - break; - } - /* Initiate the shared candidate. */ - if (xmldb_copy(h, "running", "candidate") < 0) + /* Init running db if it is not there + */ + if (xmldb_exists(h, "running") != 1) + if (xmldb_create(h, "running") < 0) + return -1; + switch (startup_mode){ + case SM_NONE: + if (startup_mode_none(h) < 0) goto done; - /* Call plugin_start with user -- options */ - if (plugin_start_useroptions(h, argv0, argc, argv) <0) + break; + case SM_INIT: /* -I */ + if (startup_mode_init(h) < 0) goto done; + break; + case SM_RUNNING: /* -CIr */ + if (startup_mode_running(h, extraxml_file) < 0) + goto done; + break; + case SM_STARTUP: /* startup configuration */ + if (startup_mode_startup(h, extraxml_file) < 0) + goto done; + break; } + /* Initiate the shared candidate. */ + if (xmldb_copy(h, "running", "candidate") < 0) + goto done; + /* Call backend plugin_start with user -- options */ + if (plugin_start_useroptions(h, argv0, argc, argv) <0) + goto done; if (once) goto done; diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index c70ae1c6..ef9af729 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -100,13 +100,9 @@ process_incoming_packet(clicon_handle h, /* Parse incoming XML message */ if (xml_parse_string(str, NULL, &xreq) < 0){ if ((cbret = cbuf_new()) == NULL){ - cprintf(cbret, "" - "operation-failed" - "rpc" - "error" - "internal error" - ""); - netconf_output(1, cb, "rpc-error"); + if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); } else clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__); diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 15c868ca..7f5df88f 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -55,6 +54,8 @@ /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + #include "restconf_lib.h" /* See RFC 8040 Section 7: Mapping from NETCONF to Status Code diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 5d084403..d8cdb27d 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -54,7 +54,7 @@ #include #include #include -#include + #include #include #include @@ -66,6 +66,8 @@ /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + /* restconf */ #include "restconf_lib.h" #include "restconf_methods.h" @@ -208,9 +210,9 @@ api_well_known(clicon_handle h, FCGX_FPrintF(r->out, "Content-Type: application/xrd+xml\r\n"); FCGX_FPrintF(r->out, "\r\n"); FCGX_SetExitStatus(200, r->out); /* OK */ - FCGX_FPrintF(r->out, "\r\n"); - FCGX_FPrintF(r->out, " \r\n"); - FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "\n"); + FCGX_FPrintF(r->out, " \n"); + FCGX_FPrintF(r->out, "\n"); return 0; } @@ -258,7 +260,7 @@ api_root(clicon_handle h, if (xml2json_cbuf(cb, xt, pretty) < 0) goto done; FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "\n\n"); retval = 0; done: if (cb) @@ -307,8 +309,8 @@ api_yang_library_version(clicon_handle h, goto done; } clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb)); - FCGX_FPrintF(r->out, "%s\r\n", cb?cbuf_get(cb):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):""); + FCGX_FPrintF(r->out, "\n\n"); retval = 0; done: if (cb) @@ -599,7 +601,7 @@ main(int argc, } else clicon_debug(1, "NULL URI"); - FCGX_Finish_r(r); + FCGX_Finish_r(r); } retval = 0; done: diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 8cadab90..78f96ece 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -104,7 +104,6 @@ Mapping netconf error-tag -> status code #include #include #include -#include #include #include #include @@ -115,6 +114,8 @@ Mapping netconf error-tag -> status code /* clicon */ #include +#include /* Need to be after clixon_xml-h due to attribute format */ + #include "restconf_lib.h" #include "restconf_methods.h" @@ -157,6 +158,7 @@ api_return_err(clicon_handle h, int retval = -1; cbuf *cb = NULL; cxobj *xtag; + char *tagstr; int code; const char *reason_phrase; @@ -167,7 +169,8 @@ api_return_err(clicon_handle h, notfound(r); /* bad reply? */ goto ok; } - code = restconf_err2code(xml_body(xtag)); + tagstr = xml_body(xtag); + code = restconf_err2code(tagstr); if ((reason_phrase = restconf_code2reason(code)) == NULL) reason_phrase=""; if (xml_name_set(xerr, "error") < 0) @@ -183,22 +186,29 @@ api_return_err(clicon_handle h, FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", use_xml?"xml":"json"); if (use_xml){ - FCGX_FPrintF(r->out, " %s", cbuf_get(cb), pretty?"\r\n":""); - FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, " \r\n"); + if (pretty){ + FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " \n"); + } + else { + FCGX_FPrintF(r->out, "", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "\n"); + } } else{ if (pretty){ - FCGX_FPrintF(r->out, "{\r\n"); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\r\n", + FCGX_FPrintF(r->out, "{\n"); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\r\n"); + FCGX_FPrintF(r->out, "}\n"); } else{ FCGX_FPrintF(r->out, "{"); FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\r\n"); + FCGX_FPrintF(r->out, "}\n"); } } ok: @@ -330,7 +340,7 @@ api_data_get2(clicon_handle h, clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "\n\n"); ok: retval = 0; done: @@ -1010,9 +1020,9 @@ api_operations_get(clicon_handle h, clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); - FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "\n"); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "\n\n"); // ok: retval = 0; done: @@ -1183,7 +1193,7 @@ api_operations_post(clicon_handle h, goto done; clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\r\n\r\n"); + FCGX_FPrintF(r->out, "\n\n"); } ok: retval = 0; diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 89110ff5..5a411420 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -124,6 +124,7 @@ main(int argc, char **argv) cxobj *xt = NULL; int i; char *xpath; + cbuf *cbret = NULL; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); @@ -261,7 +262,9 @@ main(int argc, char **argv) goto done; if (xml_rootchild(xt, 0, &xt) < 0) goto done; - if (xmldb_put(h, db, op, xt) < 0) + if ((cbret = cbuf_new()) == NULL) + goto done; + if (xmldb_put(h, db, op, xt, cbret) < 0) goto done; } else if (strcmp(cmd, "copy")==0){ @@ -325,6 +328,8 @@ main(int argc, char **argv) if (xmldb_plugin_unload(h) < 0) goto done; done: + if (cbret) + cbuf_free(cbret); if (xt) xml_free(xt); if (h) diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index b9eddc5a..bd6206a0 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -560,6 +560,7 @@ text_get(xmldb_handle xh, * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ @@ -568,7 +569,8 @@ text_modify(cxobj *x0, yang_node *y0, cxobj *x0p, cxobj *x1, - enum operation_type op) + enum operation_type op, + cbuf *cbret) { int retval = -1; char *opstr; @@ -594,8 +596,9 @@ text_modify(cxobj *x0, switch(op){ case OP_CREATE: if (x0){ - clicon_err(OE_XML, 0, "Object to create already exists"); - goto done; + if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) + goto done; + goto ok; } case OP_NONE: /* fall thru */ case OP_MERGE: @@ -631,8 +634,9 @@ text_modify(cxobj *x0, break; case OP_DELETE: if (x0==NULL){ - clicon_err(OE_XML, 0, "Object to delete does not exist"); - goto done; + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; } case OP_REMOVE: /* fall thru */ if (x0){ @@ -647,8 +651,9 @@ text_modify(cxobj *x0, switch(op){ case OP_CREATE: if (x0){ - clicon_err(OE_XML, 0, "Object to create already exists"); - goto done; + if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0) + goto done; + goto ok; } case OP_REPLACE: /* fall thru */ if (x0){ @@ -704,14 +709,18 @@ text_modify(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); yc = yang_find_datanode(y0, x1cname); - if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op) < 0) + if (text_modify(x0vec[i++], (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; } break; case OP_DELETE: if (x0==NULL){ - clicon_err(OE_XML, 0, "Object to delete does not exist"); - goto done; + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; } case OP_REMOVE: /* fall thru */ if (x0) @@ -721,27 +730,29 @@ text_modify(cxobj *x0, break; } /* CONTAINER switch op */ } /* else Y_CONTAINER */ - // ok: xml_sort(x0p, NULL); + ok: retval = 0; done: if (x0vec) free(x0vec); return retval; -} +} /* text_modify */ /*! Modify a top-level base tree x0 with modification tree x1 * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 xml tree which modifies base * @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[out] cbret Initialized cligen buffer. Contains return XML or "". * @see text_modify */ static int text_modify_top(cxobj *x0, cxobj *x1, yang_spec *yspec, - enum operation_type op) + enum operation_type op, + cbuf *cbret) { int retval = -1; char *x1cname; /* child name */ @@ -775,7 +786,9 @@ text_modify_top(cxobj *x0, else /* base tree empty */ switch(op){ case OP_DELETE: - clicon_err(OE_XML, 0, "Object to delete does not exist"); + if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0) + goto done; + goto ok; break; default: break; @@ -799,13 +812,17 @@ text_modify_top(cxobj *x0, /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (text_modify(x0c, (yang_node*)yc, x0, x1c, op) < 0) + if (text_modify(x0c, (yang_node*)yc, x0, x1c, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; } + ok: retval = 0; done: return retval; -} +} /* text_modify_top */ /*! For containers without presence and no children, remove * @param[in] x XML tree node @@ -852,7 +869,8 @@ int text_put(xmldb_handle xh, const char *db, enum operation_type op, - cxobj *x1) + cxobj *x1, + cbuf *cbret) { int retval = -1; struct text_handle *th = handle(xh); @@ -863,7 +881,15 @@ text_put(xmldb_handle xh, yang_spec *yspec; cxobj *x0 = NULL; struct db_element *de = NULL; + int cbretlocal = 0; /* Set if cbret is NULL on entry */ + if (cbret == NULL){ + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + cbretlocal++; + } if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; @@ -928,8 +954,11 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(x0, x1, yspec, op) < 0) + if (text_modify_top(x0, x1, yspec, op, cbret) < 0) goto done; + /* If xml return - ie netconf error xml tree, then stop and return OK */ + if (cbuf_len(cbret)) + goto ok; /* Remove NONE nodes if all subs recursively are also NONE */ if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) @@ -979,8 +1008,11 @@ text_put(xmldb_handle xh, } else if (clicon_xml2file(f, x0, 0, th->th_pretty) < 0) goto done; + ok: retval = 0; done: + if (cbretlocal && cbret) + cbuf_free(cbret); if (f != NULL) fclose(f); if (dbfile) @@ -1387,7 +1419,7 @@ main(int argc, op = OP_REMOVE; else usage(argv[0]); - if (xmldb_put(h, db, op, NULL, xn) < 0) + if (xmldb_put(h, db, op, NULL, xn, NULL) < 0) goto done; } else diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index 12940f89..19e9351d 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -40,7 +40,7 @@ * Prototypes */ int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); +int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); int text_dump(FILE *f, char *dbfilename, char *rxkey); int text_copy(xmldb_handle h, const char *from, const char *to); int text_lock(xmldb_handle h, const char *db, int pid); diff --git a/example/routing_backend.c b/example/routing_backend.c index 59c7388b..22f48abf 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -83,6 +83,7 @@ transaction_commit(clicon_handle h, if (debug) for (i=0; i%s", instance) < 0) + if (xml_parse_va(&xtop, NULL, "%s", cv_string_get(instance)) < 0) goto done; /* Skip top-level */ xrpc = xml_child_i(xtop, 0); diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h new file mode 100644 index 00000000..847de984 --- /dev/null +++ b/lib/clixon/clixon_netconf_lib.h @@ -0,0 +1,63 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + 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 ***** + + * Netconf library functions. See RFC6241 + * + */ +#ifndef _CLIXON_NETCONF_LIB_H +#define _CLIXON_NETCONF_LIB_H + +/* + * Prototypes + */ +int netconf_in_use(cbuf *cb, char *type, char *message); +int netconf_invalid_value(cbuf *cb, char *type, char *message); +int netconf_too_big(cbuf *cb, char *type, char *message); +int netconf_missing_attribute(cbuf *cb, char *type, char *info, char *message); +int netconf_bad_attribute(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_attribute(cbuf *cb, char *type, char *info, char *message); +int netconf_missing_element(cbuf *cb, char *type, char *info, char *message); +int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); +int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); +int netconf_access_denied(cbuf *cb, char *type, char *message); +int netconf_lock_denied(cbuf *cb, char *info, char *message); +int netconf_resource_denied(cbuf *cb, char *type, char *message); +int netconf_rollback_failed(cbuf *cb, char *type, char *message); +int netconf_data_exists(cbuf *cb, char *message); +int netconf_data_missing(cbuf *cb, char *message); +int netconf_operation_not_supported(cbuf *cb, char *type, char *message); +int netconf_operation_failed(cbuf *cb, char *type, char *message); +int netconf_malformed_message(cbuf *cb, char *message); + +#endif /* _CLIXON_NETCONF_LIB_H */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 78f043f8..6a0513ec 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -142,8 +142,11 @@ int clicon_xml2file(FILE *f, cxobj *xn, int level, int prettyprint); int clicon_xml2cbuf(cbuf *xf, cxobj *xn, int level, int prettyprint); int xml_parse_file(int fd, char *endtag, yang_spec *yspec, cxobj **xt); int xml_parse_string(const char *str, yang_spec *yspec, cxobj **xml_top); +#if defined(__GNUC__) && __GNUC__ >= 3 +int xml_parse_va(cxobj **xt, yang_spec *yspec, const char *format, ...) __attribute__ ((format (printf, 3, 4))); +#else int xml_parse_va(cxobj **xt, yang_spec *yspec, const char *format, ...); - +#endif int xmltree2cbuf(cbuf *cb, cxobj *x, int level); int xml_copy_one(cxobj *xn0, cxobj *xn1); int xml_copy(cxobj *x0, cxobj *x1); diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 49b192f8..58607396 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -78,7 +78,7 @@ typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value); typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop); /* Type of xmldb put function */ -typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt); +typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); /* Type of xmldb copy function */ typedef int (xmldb_copy_t)(xmldb_handle xh, const char *from, const char *to); @@ -139,7 +139,7 @@ int xmldb_disconnect(clicon_handle h); int xmldb_getopt(clicon_handle h, char *optname, void **value); int xmldb_setopt(clicon_handle h, char *optname, void *value); int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt); +int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret); int xmldb_copy(clicon_handle h, const char *from, const char *to); int xmldb_lock(clicon_handle h, const char *db, int pid); int xmldb_unlock(clicon_handle h, const char *db); diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 77d08d90..07e3b4a5 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -68,7 +68,7 @@ SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \ clixon_json.c clixon_yang.c clixon_yang_type.c \ clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_proto.c clixon_proto_client.c \ - clixon_xsl.c clixon_sha1.c clixon_xml_db.c + clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_netconf_lib.c YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ lex.clixon_yang_parse.o clixon_yang_parse.tab.o \ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c new file mode 100644 index 00000000..09cf1e41 --- /dev/null +++ b/lib/src/clixon_netconf_lib.c @@ -0,0 +1,698 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren + + 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 ***** + + * Netconf library functions. See RFC6241 + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon_queue.h" +#include "clixon_hash.h" +#include "clixon_err.h" +#include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_netconf_lib.h" + +/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A + * + * The request requires a resource that already is in use. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_in_use(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "in-use" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf invalid-value error XML tree according to RFC 6241 Appendix A + * + * The request specifies an unacceptable value for one or more parameters. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_invalid_value(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "invalid-value" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf too-big error XML tree according to RFC 6241 Appendix A + * + * The request or response (that would be generated) is + * too large for the implementation to handle. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "transport", "rpc", "application", "protocol" + * @param[in] message Error message + */ +int +netconf_too_big(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "too-big" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf missing-attribute error XML tree according to RFC 6241 App A + * + * An expected attribute is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "missing-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf bad-attribute error XML tree according to RFC 6241 App A + * + * An attribute value is not correct; e.g., wrong type, + * out of range, pattern mismatch. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_bad_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "bad-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknwon-attribute error XML tree according to RFC 6241 App A + * + * An unexpected attribute is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] info bad-attribute or bad-element xml + * @param[in] message Error message + */ +int +netconf_unknown_attribute(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "unknown-attribute" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + + +/*! Create Netconf missing-element error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_missing_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "missing-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf bad-element error XML tree according to RFC 6241 App A + * + * An element value is not correct; e.g., wrong type, out of range, + * pattern mismatch. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_bad_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "bad-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknown-element error XML tree according to RFC 6241 App A + * + * An unexpected element is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element xml + * @param[in] message Error message + */ +int +netconf_unknown_element(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + + if (cprintf(cb, "" + "unknown-element" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf unknown-namespace error XML tree according to RFC 6241 App A + * + * An unexpected namespace is present. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] info bad-element or bad-namespace xml + * @param[in] message Error message + */ +int +netconf_unknown_namespace(cbuf *cb, + char *type, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "unknown-namespace" + "%s" + "%s" + "error", + type, info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf access-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_access_denied(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "access-denied" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf lock-denied error XML tree according to RFC 6241 App A + * + * Access to the requested lock is denied because the lock is currently held + * by another entity. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] info session-id xml + * @param[in] message Error message + */ +int +netconf_lock_denied(cbuf *cb, + char *info, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "lock-denied" + "protocol" + "%s" + "error", + info) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf resource-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "transport, "rpc", "application", "protocol" + * @param[in] message Error message + */ +int +netconf_resource_denied(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "resource-denied" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf rollback-failed error XML tree according to RFC 6241 App A + * + * Request to roll back some configuration change (via rollback-on-error or + * operations) was not completed for some reason. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_rollback_failed(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "rollback-failed" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf data-exists error XML tree according to RFC 6241 Appendix A + * + * Request could not be completed because the relevant + * data model content already exists. For example, + * a "create" operation was attempted on data that already exists. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + */ +int +netconf_data_exists(cbuf *cb, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "data-exists" + "application" + "error") <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf data-missing error XML tree according to RFC 6241 App A + * + * Request could not be completed because the relevant data model content + * does not exist. For example, a "delete" operation was attempted on + * data that does not exist. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + */ +int +netconf_data_missing(cbuf *cb, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "data-missing" + "application" + "error") <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf operation-not-supported error XML according to RFC 6241 App A + * + * Request could not be completed because the requested operation is not + * supported by this implementation. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_not_supported(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "operation-not-supported" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} + +/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A + * + * Request could not be completed because the requested operation failed for + * some reason not covered by any other error condition. + * @param[out] cb CLIgen buf. Error XML is written in this buffer + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_failed(cbuf *cb, + char *type, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "operation-failed" + "%s" + "error", + type) <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + 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] cb CLIgen buf. Error XML is written in this buffer + * @param[in] message Error message + * @note New in :base:1.1 + */ +int +netconf_malformed_message(cbuf *cb, + char *message) +{ + int retval = -1; + + if (cprintf(cb, "" + "malformed-message" + "rpc" + "error") <0) + goto err; + if (message && cprintf(cb, "%s", message) < 0) + goto err; + if (cprintf(cb, "") <0) + goto err; + retval = 0; + done: + return retval; + err: + clicon_err(OE_XML, errno, "cprintf"); + goto done; +} diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 75286ed2..eaaf33c4 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -57,7 +57,6 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_string.h" - #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" @@ -1215,8 +1214,8 @@ xmltree2cbuf(cbuf *cb, */ static int _xml_parse(const char *str, - yang_spec *yspec, - cxobj *xt) + yang_spec *yspec, + cxobj *xt) { int retval = -1; struct xml_parse_yacc_arg ya = {0,}; @@ -1424,10 +1423,7 @@ xml_parse_va(cxobj **xtop, va_start(args, format); len = vsnprintf(str, len, format, args) + 1; va_end(args); - if (*xtop == NULL) - if ((*xtop = xml_new(XML_TOP_SYMBOL, NULL, NULL)) == NULL) - goto done; - if (_xml_parse(str, yspec, *xtop) < 0) + if (xml_parse_string(str, yspec, xtop) < 0) goto done; retval = 0; done: diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index d3738f37..42e5c870 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -322,7 +322,7 @@ xmldb_setopt(clicon_handle h, * @param[in] dbname Name of database to search in (filename including dir path * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in] config If set only configuration data, else also state - * @param[out] xtop Single XML tree. Free with xml_free() + * @param[out] xret Single return XML tree. Free with xml_free() * @retval 0 OK * @retval -1 Error * @code @@ -339,7 +339,7 @@ xmldb_get(clicon_handle h, const char *db, char *xpath, int config, - cxobj **xtop) + cxobj **xret) { int retval = -1; xmldb_handle xh; @@ -357,11 +357,11 @@ xmldb_get(clicon_handle h, clicon_err(OE_DB, 0, "Not connected to datastore plugin"); goto done; } - retval = xa->xa_get_fn(xh, db, xpath, config, xtop); + retval = xa->xa_get_fn(xh, db, xpath, config, xret); #if DEBUG if (retval == 0) { cbuf *cb = cbuf_new(); - clicon_xml2cbuf(cb, *xtop, 0, 0); + clicon_xml2cbuf(cb, *xret, 0, 0); clicon_log(LOG_WARNING, "%s: db:%s xpath:%s xml:%s", __FUNCTION__, db, xpath, cbuf_get(cb)); cbuf_free(cb); @@ -377,25 +377,29 @@ xmldb_get(clicon_handle h, * @param[in] db running or candidate * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy + * @param[out] cbret Initialized cligen buffer or NULL. On exit contains XML or "". * @retval 0 OK * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. * @code * cxobj *xt; + * cxobj *xret = NULL; * if (xml_parse_string("17", yspec, &xt) < 0) * err; - * if (xmldb_put(xh, "running", OP_MERGE, xt) < 0) + * if (xmldb_put(xh, "running", OP_MERGE, xt, cbret) < 0) * err; * @endcode * @note that you can add both config data and state data. In comparison, * xmldb_get has a parameter to get config data only. + * @note if xret is non-null, it may contain error message * */ int xmldb_put(clicon_handle h, const char *db, enum operation_type op, - cxobj *xt) + cxobj *xt, + cbuf *cbret) { int retval = -1; xmldb_handle xh; @@ -425,7 +429,7 @@ xmldb_put(clicon_handle h, cbuf_free(cb); } #endif - retval = xa->xa_put_fn(xh, db, op, xt); + retval = xa->xa_put_fn(xh, db, op, xt, cbret); done: return retval; } diff --git a/test/lib.sh b/test/lib.sh index 2872126e..61dc3c84 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -56,9 +56,9 @@ expectfn(){ expect2= fi ret=`$cmd` - if [ $? -ne 0 ]; then - err "wrong args" - fi +# if [ $? -ne 0 ]; then +# err "wrong args" +# fi # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return @@ -68,9 +68,7 @@ expectfn(){ fi # grep extended grep match=`echo $ret | grep -EZo "$expect"` -# echo "ret:\"$ret\"" -# echo "expect:\"$expect\"" -# echo "match:\"$match\"" + if [ -z "$match" ]; then err "$expect" "$ret" fi @@ -82,6 +80,37 @@ expectfn(){ fi } +# Similar to expectfn, but checks for equality and not only match +expecteq2(){ + ret=$1 + expect=$2 + # Match if both are empty string + if [ -z "$ret" -a -z "$expect" ]; then + return + fi + if [ "$ret" != "$expect" ]; then + err "$expect" "$ret" + fi +} + +# Similar to expectfn, but checks for equality and not only match +expecteq(){ + cmd=$1 + expect=$2 + ret=$($cmd) + + if [ $? -ne 0 ]; then + err "wrong args" + fi + # Match if both are empty string + if [ -z "$ret" -a -z "$expect" ]; then + return + fi + if [ "$ret" != "$expect" ]; then + err "$expect" "$ret" + fi +} + # clixon tester. First arg is command second is stdin and # third is expected outcome expecteof(){ diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 6f7ae875..b0714b4d 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -55,7 +55,7 @@ run(){ rm -rf $mydir/* conf="-d candidate -b $mydir -p ../datastore/$name/$name.so -y $dir -m ietf-ip" - echo "conf:$conf" + new "datastore $name init" expectfn "$datastore $conf init" "" diff --git a/test/test_leafref.sh b/test/test_leafref.sh index bb5649ff..789ca4f8 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -88,8 +88,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth3
10.0.4.6
]]>]]>" "^]]>]]>$" -new "leafref validate" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^missing-attribute" +new "leafref validate XXX shouldnt really be operation-failed, more work in validate code" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" @@ -109,7 +109,7 @@ new "leafref delete leaf" expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth0]]>]]>" "^" new "leafref validate (should fail)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^missing-attribute" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^operation-failed" new "leafref discard-changes" expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 78070864..dae18487 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -34,7 +34,7 @@ if [ $? -ne 0 ]; then fi new "start backend -s init -f $cfg" # start new backend -sudo clixon_backend -s init -f $cfg +sudo clixon_backend -s init -f $cfg # -D 1 if [ $? -ne 0 ]; then err fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 99920060..c6b22734 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -63,7 +63,7 @@ if [ $? -ne 0 ]; then err fi new "start backend -s init -f $cfg -y $fyang" -sudo clixon_backend -s init -f $cfg -y $fyang +sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi @@ -72,28 +72,26 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D sleep 1 new "restconf tests" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" -expectfn "curl -s -X GET http://localhost/.well-known/host-meta" "" +expecteq2 "$(curl -s -X GET http://localhost/.well-known/host-meta)" " + +" new "restconf get restconf resource. RFC 8040 3.3 (json)" -expectfn "curl -sG http://localhost/restconf" '{"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' +expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' new "restconf get restconf resource. RFC 8040 3.3 (xml)" -ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) -expect="2016-06-21" -match=`echo $ret | grep -EZo "$expect"` -if [ -z "$match" ]; then - err "$expect" "$ret" -fi +# Get XML instead of JSON? +expecteq2 $(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) '2016-06-21' new "restconf get restconf/operations. RFC8040 3.3.2" -expectfn "curl -sG http://localhost/restconf/operations" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}' +expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) @@ -104,7 +102,7 @@ if [ -z "$match" ]; then fi new "restconf get restconf/yang-library-version. RFC8040 3.3.3" -expectfn "curl -sG http://localhost/restconf/yang-library-version" '{"yang-library-version": "2016-06-21"}' +expecteq2 "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) @@ -122,10 +120,10 @@ expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expectfn 'curl -s -X POST -d {"input":{"name":""}} http://localhost/restconf/operations/ex:empty' '{"output": null}' +expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}' new "restconf get empty config + state json" -expectfn "curl -sSG http://localhost/restconf/data" "{\"data\": $state}" +expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) @@ -136,7 +134,7 @@ if [ -z "$match" ]; then fi new "restconf get data/interfaces-state/interface=eth0 json" -expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0" '{"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}' +expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh @@ -148,8 +146,7 @@ if [ -z "$match" ]; then fi new "restconf get state operation eth0 type json" -expectfn "curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type" '{"type": "eth"} -$' +expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh @@ -161,76 +158,78 @@ if [ -z "$match" ]; then fi new "restconf GET datastore" -expectfn "curl -s -X GET http://localhost/restconf/data" "data" +expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' +# Exact match new "restconf Add subtree to datastore using POST" -ret=$(curl -s -i -X POST -H "Accept: application/yang-data+json" -d '{"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}}' http://localhost/restconf/data) -expect="HTTP/1.1 200 OK" -match=`echo $ret | grep -EZo "$expect"` -if [ -z "$match" ]; then - err "$expect" "$ret" -fi +expectfn 'curl -s -i -X POST -H "Accept: application/yang-data+json" -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' 'HTTP/1.1 200 OK' new "restconf Re-add subtree which should give error" -expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Object to create already exists"}}}' +expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' + +# XXX Cant get this to work +#expecteq2 "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} $' new "restconf delete interfaces" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces' "" +expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" new "restconf Check empty config" -expectfn "curl -sG http://localhost/restconf/data" $state +expectfn "curl -sG http://localhost/restconf/data" "$state" new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "" +# XXX cant get this to work +#expecteq2 "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new "restconf Check eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' new "restconf Re-post eth/0/0 which should generate error" -expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' 'Object to create already exists' +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"}}}' new "Add leaf description using POST" -expectfn 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +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" new "restconf Check description added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}\]} -$' +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}]}}}' new "restconf delete eth/0/0" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' $state new "restconf Re-Delete eth/0/0 using none should generate error" -expectfn 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '"error-message": "Object to delete does not exist"' +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"}}}' new "restconf Add subtree eth/0/0 using PUT" -expectfn 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" new "restconf get subtree" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' new "restconf rpc using POST json" -expectfn '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"}}}}' +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"}}}}' new "restconf rpc using POST xml" -# Cant get shell macros to work, inline matching from lib.sh ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) expect="ipv42.3.4.5" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi +# XXX cant get -H to work +#expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5' + +# Cant get shell macros to work, inline matching from lib.sh + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index db275dc1..70e84bfa 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -84,10 +84,10 @@ new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" new "restconf POST again" -expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "Object to create already exists" +expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf POST from top" -expectfn 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' "Object to create already exists" +expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" From ba3ca7e07f6e27d5f3f6ee1ccf9665e3e7579e08 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 20 Mar 2018 18:39:24 +0100 Subject: [PATCH 18/50] added clixon_netconf_lib.h --- lib/clixon/clixon.h.in | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/clixon/clixon.h.in b/lib/clixon/clixon.h.in index 9422c501..05432227 100644 --- a/lib/clixon/clixon.h.in +++ b/lib/clixon/clixon.h.in @@ -83,6 +83,7 @@ #include #include #include +#include /* * Global variables generated by Makefile From 835674bbe077155babf324b04437395eef138f96 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 22 Mar 2018 21:33:41 +0100 Subject: [PATCH 19/50] \r\n in restconf code and tests --- apps/restconf/restconf_main.c | 4 +-- apps/restconf/restconf_methods.c | 27 ++++++++++-------- doc/FAQ.md | 10 +++---- lib/clixon/clixon_plugin.h | 10 +++++-- test/lib.sh | 5 +++- test/test_restconf.sh | 48 ++++++++++++++++++++------------ test/test_restconf2.sh | 4 +-- 7 files changed, 66 insertions(+), 42 deletions(-) diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index d8cdb27d..309c5223 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -212,7 +212,7 @@ api_well_known(clicon_handle h, FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "\n"); FCGX_FPrintF(r->out, " \n"); - FCGX_FPrintF(r->out, "\n"); + FCGX_FPrintF(r->out, "\r\n"); return 0; } @@ -260,7 +260,7 @@ api_root(clicon_handle h, if (xml2json_cbuf(cb, xt, pretty) < 0) goto done; FCGX_FPrintF(r->out, "%s", cb?cbuf_get(cb):""); - FCGX_FPrintF(r->out, "\n\n"); + FCGX_FPrintF(r->out, "\r\n\r\n"); retval = 0; done: if (cb) diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 78f96ece..fde9c4dc 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -189,12 +189,12 @@ api_return_err(clicon_handle h, if (pretty){ FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, " \n"); + FCGX_FPrintF(r->out, " \r\n"); } else { FCGX_FPrintF(r->out, "", cbuf_get(cb)); FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, "\n"); + FCGX_FPrintF(r->out, "\r\n"); } } else{ @@ -202,13 +202,13 @@ api_return_err(clicon_handle h, FCGX_FPrintF(r->out, "{\n"); FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\n"); + FCGX_FPrintF(r->out, "}\r\n"); } else{ FCGX_FPrintF(r->out, "{"); FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\n"); + FCGX_FPrintF(r->out, "}\r\n"); } } ok: @@ -234,6 +234,7 @@ api_return_err(clicon_handle h, * @code * curl -G http://localhost/restconf/data/interfaces/interface=eth0 * @endcode + * See RFC8040 Sec 4.2 and 4.3 * XXX: cant find a way to use Accept request field to choose Content-Type * I would like to support both xml and json. * Request may contain @@ -337,10 +338,9 @@ api_data_get2(clicon_handle h, if (xml2json_cbuf_vec(cbx, xvec, xlen, pretty) < 0) goto done; } - clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\n\n"); + FCGX_FPrintF(r->out, "\r\n\r\n"); ok: retval = 0; done: @@ -437,6 +437,8 @@ api_data_get(clicon_handle h, * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * @note restconf POST is mapped to edit-config create. + * See RFC8040 Sec 4.4.1 + POST: target resource type is datastore --> create a top-level resource target resource type is data resource --> create child resource @@ -520,7 +522,7 @@ api_data_post(clicon_handle h, badrequest(r); goto ok; } - /* The message-body MUST contain exactly one instance of the + /* 4.4.1: The message-body MUST contain exactly one instance of the * expected data resource. */ if (xml_child_nr(xdata) != 1){ @@ -649,6 +651,7 @@ match_list_keys(yang_stmt *y, * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data * @note restconf PUT is mapped to edit-config replace. + * See RFC8040 Sec 4.5 * @example curl -X PUT -d '{"enabled":"false"}' http://127.0.0.1/restconf/data/interfaces/interface=eth1 * @@ -823,6 +826,7 @@ api_data_put(clicon_handle h, * @param[in] data Stream input data * @param[in] username Authenticated user * Netconf: (nc:operation="merge") + * See RFC8040 Sec 4.6 */ int api_data_patch(clicon_handle h, @@ -846,6 +850,7 @@ api_data_patch(clicon_handle h, * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML + * See RFC 8040 Sec 4.7 * Example: * curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0 * Netconf: (nc:operation="delete") @@ -1020,9 +1025,9 @@ api_operations_get(clicon_handle h, clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); - FCGX_FPrintF(r->out, "\n"); + FCGX_FPrintF(r->out, "\r\n"); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\n\n"); + FCGX_FPrintF(r->out, "\r\n\r\n"); // ok: retval = 0; done: @@ -1046,7 +1051,7 @@ api_operations_get(clicon_handle h, * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data - + * See RFC 8040 Sec 3.6 / 4.4.2 * @note We map post to edit-config create. POST {+restconf}/operations/ */ @@ -1193,7 +1198,7 @@ api_operations_post(clicon_handle h, goto done; clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); - FCGX_FPrintF(r->out, "\n\n"); + FCGX_FPrintF(r->out, "\r\n\r\n"); } ok: retval = 0; diff --git a/doc/FAQ.md b/doc/FAQ.md index ee04a409..0debf435 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -83,11 +83,11 @@ configuration file is installed at /usr/local/etc/routing.xml. The YANG specification for the configuration file is clixon-config.yang. You can change where CLixon looks for the configuration FILE as follows: - # Provide -f FILE option when starting a program (eg clixon_backend -f FILE) - # Provide --with-configfile=FILE when configuring - # Provide --with-sysconfig= when configuring, then FILE is /clixon.xml - # Provide --sysconfig= when configuring then FILE is /etc/clixon.xml - # FILE is /usr/local/etc/clixon.xml + - Provide -f FILE option when starting a program (eg clixon_backend -f FILE) + - Provide --with-configfile=FILE when configuring + - Provide --with-sysconfig= when configuring, then FILE is /clixon.xml + - Provide --sysconfig= when configuring then FILE is /etc/clixon.xml + - FILE is /usr/local/etc/clixon.xml ## Can I run Clixon as docker containers? Yes, the example works as docker containers as well. backend and cli needs a diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 22ef53b5..f102de3d 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -73,12 +73,16 @@ typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */ #define PLUGIN_EXIT "plugin_exit" typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ -/*! Called by restconf - * Returns 0 if credentials OK, -1 if failed +/*! Called by restconf to ceck credentials and return username */ #define PLUGIN_CREDENTIALS "plugin_credentials" + /* Plugin credentials - * username should be freed after use + * @param[in] Clicon handle + * @param[in] void*, eg Fastcgihandle request restconf + * @param[out] username should be freed after use + * @retval 0 if credentials OK + * @retval -1 credentials not OK */ typedef int (plgcredentials_t)(clicon_handle, void *, char **username); diff --git a/test/lib.sh b/test/lib.sh index 61dc3c84..b6964e2e 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -50,12 +50,14 @@ new(){ expectfn(){ cmd=$1 expect=$2 + if [ $# = 3 ]; then expect2=$3 else expect2= fi - ret=`$cmd` + ret=$($cmd) + # if [ $? -ne 0 ]; then # err "wrong args" # fi @@ -84,6 +86,7 @@ expectfn(){ expecteq2(){ ret=$1 expect=$2 + # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return diff --git a/test/test_restconf.sh b/test/test_restconf.sh index c6b22734..7562a8f4 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -81,17 +81,20 @@ new "restconf tests" new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" expecteq2 "$(curl -s -X GET http://localhost/.well-known/host-meta)" " -" + " new "restconf get restconf resource. RFC 8040 3.3 (json)" -expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}' +expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}} + ' new "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? -expecteq2 $(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf) '2016-06-21' +expecteq2 "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 + ' -new "restconf get restconf/operations. RFC8040 3.3.2" -expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}' +new "restconf get restconf/operations. RFC8040 3.3.2 (json)" +expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}} + ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) @@ -120,10 +123,12 @@ expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}' +expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null} + ' new "restconf get empty config + state json" -expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' +expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' new "restconf get empty config + state xml" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/data) @@ -134,7 +139,8 @@ if [ -z "$match" ]; then fi new "restconf get data/interfaces-state/interface=eth0 json" -expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}' +expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]} + ' new "restconf get state operation eth0 xml" # Cant get shell macros to work, inline matching from lib.sh @@ -146,7 +152,8 @@ if [ -z "$match" ]; then fi new "restconf get state operation eth0 type json" -expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}' +expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"} + ' new "restconf get state operation eth0 type xml" # Cant get shell macros to work, inline matching from lib.sh @@ -158,7 +165,8 @@ if [ -z "$match" ]; then fi new "restconf GET datastore" -expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' +expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' # Exact match new "restconf Add subtree to datastore using POST" @@ -171,8 +179,8 @@ expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type" #expecteq2 "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" -expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} -$' +expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} + ' new "restconf delete interfaces" expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" @@ -186,10 +194,11 @@ expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enable #expecteq2 "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" new "restconf Check eth/0/0 added" -expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' +expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' new "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"}}}' +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"}}} ' 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' "" @@ -198,7 +207,8 @@ 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" new "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}]}}}' +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}]}}} + ' new "restconf delete eth/0/0" expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" @@ -207,16 +217,18 @@ new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' $state new "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"}}}' +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"}}} ' 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' "" new "restconf get subtree" -expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}' +expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} + ' new "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"}}}}' +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"}}}} + ' new "restconf rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 70e84bfa..37bb8708 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -84,10 +84,10 @@ new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" new "restconf POST again" -expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf POST from top" -expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" From cce76faa792d3d0c252a6c35575371742a3ea37a Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 24 Mar 2018 14:37:36 +0100 Subject: [PATCH 20/50] Fixed three-key list entry problem (reported by jdl@netgate) --- CHANGELOG.md | 1 + lib/src/clixon_xml_sort.c | 35 ++++++++++++++++++++--------------- test/test_yang.sh | 39 +++++++++++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80851261..eb894f43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs +* Fixed three-key list entry problem (reported by jdl@netgate) * Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 0016a59b..e3aa4b23 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -167,7 +167,7 @@ xml_cmp(const void* arg1, return equal; } -/*! +/*! Compare xml object * @param[in] yangi Yang order * @param[in] keynr Length of keyvec/keyval vector when applicable * @param[in] keyvec Array of of yang key identifiers @@ -192,36 +192,38 @@ xml_cmp1(cxobj *x, int i; char *keyname; char *key; + int match = 0; /* Check if same yang spec (order in yang stmt list) */ switch (keyword){ case Y_CONTAINER: /* Match with name */ case Y_LEAF: /* Match with name */ - return strcmp(name, xml_name(x)); + match = strcmp(name, xml_name(x)); break; case Y_LEAF_LIST: /* Match with name and value */ if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL) *userorder=1; b=xml_body(x); - return strcmp(keyval[0], b); + match = strcmp(keyval[0], b); break; case Y_LIST: /* Match with array of key values */ if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL) *userorder=1; + /* All must match */ for (i=0; ie0 given "name" */ if ((b = xml_find_body(x, keyname)) == NULL) break; /* error case */ - return strcmp(key, b); + if ((match = strcmp(key, b)) != 0) + break; } - return 0; break; default: break; } - return 0; /* should not reach here */ + return match; /* should not reach here */ } /*! Sort children of an XML node @@ -273,12 +275,12 @@ xml_search_userorder(cxobj *x0, } /*! - * @param[in] yangi Yang order - * @param[in] keynr Length of keyvec/keyval vector when applicable - * @param[in] keyvec Array of of yang key identifiers - * @param[in] keyval Array of of yang key values - * @param[in] low Lower bound of childvec search interval - * @param[in] upper Lower bound of childvec search interval + * @param[in] yangi Yang order + * @param[in] keynr Length of keyvec/keyval vector when applicable + * @param[in] keyvec Array of of yang key identifiers + * @param[in] keyval Array of of yang key values + * @param[in] low Lower bound of childvec search interval + * @param[in] upper Lower bound of childvec search interval */ static cxobj * xml_search1(cxobj *x0, @@ -522,7 +524,7 @@ xml_sort_verify(cxobj *x0, return retval; } -/*! Given child tree x1c, find matching child in base tree x0 +/*! Given child tree x1c, find matching child in base tree x0 and return as x0cp * param[in] x0 Base tree node * param[in] x1c Modification tree child * param[in] yc Yang spec of tree child @@ -567,7 +569,10 @@ match_base_child(cxobj *x0, break; case Y_LIST: /* Match with key values */ cvk = yc->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ - /* Count number of key indexes */ + /* Count number of key indexes + * Then create two vectors one with names and one with values of x1c, + * ec: keyvec: [a,b,c] keyval: [1,2,3] + */ cvi = NULL; keynr = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) keynr++; @@ -591,7 +596,7 @@ match_base_child(cxobj *x0, default: break; } - /* Get match */ + /* Get match. Sorting mode(optimized) or not?*/ if (xml_child_sort==0) *x0cp = xml_match(x0, xml_name(x1c), yc->ys_keyword, keynr, keyvec, keyval); else{ diff --git a/test/test_yang.sh b/test/test_yang.sh index b53ad8d3..4d5af03d 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -34,7 +34,7 @@ module example{ ex:c-define "MY_INTERFACES"; container x { list y { - key "a b"; + key "a b c"; leaf a { type string; } @@ -44,6 +44,9 @@ module example{ leaf c { type string; } + leaf val { + type string; + } } leaf d { type empty; @@ -115,7 +118,7 @@ new "cli not defined extension" #expectfn "$clixon_cli -1f $cfg -y $fyangerr show version" "Yang error: Extension ex:not-defined not found" new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "125]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "125one]]>]]>" "^]]>]]>$" new "netconf commit" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" @@ -125,7 +128,7 @@ new "netconf commit 2nd" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" new "netconf edit leaf-list" expecteof "$clixon_netconf -qf $cfg -y $fyang" "hejhopp]]>]]>" "^]]>]]>$" @@ -137,7 +140,7 @@ new "netconf get leaf-list path" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^hejhopp]]>]]>$" new "netconf get (should be some)" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^125one]]>]]>$" new "cli set leaf-list" expectfn "$clixon_cli -1f $cfg -y $fyang set x f e foo" "" @@ -162,6 +165,34 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +new "netconf delete candidate" +expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" + +# Check 3-keys +new "netconf add one 3-key entry" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "111one]]>]]>" "^]]>]]>$" + +new "netconf check add one 3-key" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one]]>]]>' + +new "netconf add another (with same 1st key)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "121two]]>]]>" "^]]>]]>$" + +new "netconf check add another" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111one121two]]>]]>' + +new "netconf replace first" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "111replace]]>]]>" "^]]>]]>$" + +new "netconf check replace" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '111replace121two]]>]]>' + +new "netconf delete first" +expecteof "$clixon_netconf -qf $cfg -y $fyang" '111]]>]]>' "^]]>]]>$" + +new "netconf check delete" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '121two]]>]]>' + # Check if still alive pid=`pgrep clixon_backend` if [ -z "$pid" ]; then From bfce20c7604230dae936700efc938a3e9028fd4c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Mar 2018 15:47:27 +0200 Subject: [PATCH 21/50] * Added Clixon Restconf library * Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h * The restconf library can be included by a restconf plugin. * Example code in example/Makefile.in and example/restconf_lib.c * Authorization * Example extended with authorization * Test added with http basic authorization (test/test_auth.sh) * Documentation in FAQ.md * README.md extended with new yang, netconf, restconf, datastore, and auth sections. --- CHANGELOG.md | 9 + README.md | 184 ++++++++++++++------ apps/netconf/Makefile.in | 1 + apps/netconf/netconf_main.c | 1 - apps/restconf/Makefile.in | 49 +++++- apps/restconf/README.md | 12 -- apps/restconf/clixon_restconf.h | 71 ++++++++ apps/restconf/restconf_lib.c | 3 +- doc/FAQ.md | 20 ++- example/Makefile.in | 25 ++- example/example.yang | 17 ++ example/routing_restconf.c | 287 ++++++++++++++++++++++++++++++++ lib/clixon/clixon_plugin.h | 18 +- test/lib.sh | 39 ++--- test/test_auth.sh | 104 ++++++++++++ test/test_restconf.sh | 82 ++++----- test/test_restconf2.sh | 8 +- 17 files changed, 773 insertions(+), 157 deletions(-) create mode 100644 apps/restconf/clixon_restconf.h create mode 100644 example/routing_restconf.c create mode 100755 test/test_auth.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index eb894f43..3c08b5de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ ### Major changes: +* Added Clixon Restconf library + * Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h + * The restconf library can be included by a restconf plugin. + * Example code in example/Makefile.in and example/restconf_lib.c +* Authorization + * Example extended with authorization + * Test added with http basic authorization (test/test_auth.sh) + * Documentation in FAQ.md * Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). * Proper RFC 6241 Netconf error handling * New functions added in clixon_netconf_lib.[ch] @@ -12,6 +20,7 @@ ### Minor changes: +* README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. * Add username to rpc calls to prepare for authorization for backend: clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) diff --git a/README.md b/README.md index 141e78b6..301ceee8 100644 --- a/README.md +++ b/README.md @@ -4,26 +4,43 @@ Clixon is an automatic configuration manager where you generate interactive CLI, NETCONF, RESTCONF and embedded databases with transaction support from a YANG specification. -Table of contents -================= - * [Documentation](#documentation) - * [Installation](#installation) - * [Dependencies](#dependencies) - * [Licenses](#licenses) +Topics +====== * [Background](#background) + * [Frequently asked questions](doc/FAQ.md) + * [Installation](#installation) + * [Licenses](#licenses) + * [Support](#support) + * [Dependencies](#dependencies) + * [Extending](#extending) + * [Yang](#yang) + * [Netconf](#netconf) + * [Restconf](#restconf) + * [Datastore](datastore/README.md) + * [Authentication and Authorization](#auth) + * [Example](example/README.md) + * [Changelog](CHANGELOG.md) recent changes. * [Clixon SDK](#SDK) + * [Clicon and Clixon project page](http://www.clicon.org) + * [Tests](test/README.md) + * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) + +Background +========== -Documentation -============= -- [Frequently asked questions](doc/FAQ.md) -- [CHANGELOG](CHANGELOG.md) recent changes. -- [XML datastore](datastore/README.md) -- [Netconf support](apps/netconf/README.md) -- [Restconf support](apps/restconf/README.md) -- [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) -- [Routing example](example/README.md) -- [Clicon and Clixon project page](http://www.clicon.org) -- [Tests](test/README.md) +Clixon was implemented to provide an open-source generic configuration +tool. The existing [CLIgen](http://www.cligen.se) tool was extended to +a framework. Most of the user projects are for embedded network and +measuring devices, but can be deployed for more general use. + +Users of clixon currently include: + * [Netgate](https://www.netgate.com) + * [CloudMon360](http://cloudmon360.com) + * [Grideye](http://hagsand.se/grideye) + * [Netclean](https://www.netclean.com/solutions/whitebox) (only CLIgen) + * [Prosilient's PTAnalyzer] (only CLIgen) + +See also [Clicon project page](http://clicon.org). Installation ============ @@ -38,6 +55,13 @@ A typical installation is as follows: One [example application](example/README.md) is provided, a IETF IP YANG datamodel with generated CLI and configuration interface. +Licenses +======== +Clixon is open-source and dual licensed. Either Apache License, Version 2.0 or GNU +General Public License Version 2; you choose. + +See [LICENSE.md](LICENSE.md) for the license. + Dependencies ============ Clixon depends on the following software packages, which need to exist on the target machine. @@ -50,53 +74,105 @@ to build and install CLIgen: - Yacc/bison - Lex/Flex - Fcgi (if restconf is enabled) -- [Qdbm](http://fallabs.com/qdbm/) key-value store (if keyvalue datastore is enabled) There is no yum/apt/ostree package for Clixon (please help?) -Licenses +Support +======= +Clixon interaction is best done posting issues, pull requests, or joining the [slack channel](https://join.slack.com/t/clixondev/shared_invite/enQtMzI3OTM4MzA3Nzk3LTA3NWM4OWYwYWMxZDhiYTNhNjRkNjQ1NWI1Zjk5M2JjMDk4MTUzMTljYTZiYmNhODkwMDI2ZTkyNWU3ZWMyN2U). + +Extending +========= +Clixon provides a core system and can be used as-is using available +Yang specifications. However, an application very quickly needs to +specialize funxtions. Clixon is extended by (most commonly) writing +plugins for cli and backend. Extensions for netconf and restconf +are also available. + +Plugins are written in C and easiest is to look at +[example](example/README.md) or consulting the [FAQ](doc/FAQ.md). + +Yang +==== + +YANG and XML is at the heart of Clixon. Yang modules are used as a +specification for handling XML configuration data. The YANG spec is +used to generate an interactive CLI, netconf and restconf clients. It +also manages an XML datastore. + +Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions: +- conformance: feature, if-feature, deviation +- identity, base, identityref +- list features: min/max-elements, unique + +The aim is also to cover new featires in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) + +Clixon has its own XML library designed for performance. + +Netconf +======= +Clixon implements the following NETCONF proposals or standards: +- [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt) +- [Using the NETCONF Configuration Protocol over Secure SHell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt) +- [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt) + +Some updates are being made to RFC 6241 and RFC 6242. + +Clixon does not support the following features: + +- :url capability +- copy-config source config +- edit-config testopts +- edit-config erropts +- edit-config config-text + +Restconf ======== -Clixon is dual licensed. Either Apache License, Version 2.0 or GNU -General Public License Version 2; you choose. +Clixon restconf is a daemon based on FASTCGI. Instructions are available to +run with NGINX. +The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040). +The following features are supported: +- OPTIONS, HEAD, GET, POST, PUT, DELETE +The following are not implemented +- PATCH +- query parameters (section 4.9) +- notifications (sec 6) +- schema resource -See [LICENSE.md](LICENSE.md) for the license. +See [more detailed restconf instructions](apps/restconf/README.md). -Background -========== -We implemented Clixon since we needed a generic configuration tool in -several projects, including -[KTH](http://www.csc.kth.se/~olofh/10G_OSR). Most of these projects -were for embedded network and measuring-probe devices. We started with -something called Clicon which was based on a key-value specification -and data-store. But as time passed new standards evolved and we -started adapting it to XML, Yang and netconf. Finally we made Clixon, -where the legacy key specification has been replaced completely by -YANG and using XML as configuration data. This means that legacy -Clicon applications do not run on Clixon. +Datastore +========= +The Clixon datastore is a stand-alone XML based datastore. The idea is +to be able to use different datastores backends with the same +API. + +Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented. + +The datastore is primarily designed to be used by Clixon but can be used +separately. + +See [more detailed restconf instructions](datastore/README.md). + + +Auth +==== + +Authentication is not in-scope for Clixon, however, there is ongoing work +to implement [NACM](https://tools.ietf.org/html/rfc8341). + +There are hooks (plugin callbacks) to identify which user is accessing a +client. That identity can then be used for authorization. + +In short, authentication needs to be coupled to clixon clients: + * CLI - Login has already been made via SSH + * Netconf - SSH netconf subsystem + * Restconf needs credentials. See [FAQ](doc/FAQ.md#How-do-I-write-an-authentication-callback). The [Example](example/README.md) has an example how to do this with HTTP basic auth. It is possible for do this for more advanced mechanisms such as Oauth2 or [https://github.com/CESNET/Netopeer2/tree/master/server/configuration] SDK === -clixon sdk +clixon sdk The figure shows the SDK runtime of Clixon. -YANG and XML is at the heart of Clixon. Yang modules are used as a -specification for handling XML configuration data. The spec is also -used to generate an interactive CLI client as well as provide -[Netconf](apps/netconf/README.md) and -[Restconf](apps/restconf/README.md) clients. - -The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions: -- conformance: feature, if-feature, deviation -- identity, base, identityref -- list features: min/max-elements, unique, ordered-by - -There are also new features in YANG 1.1 [YANG RFC -7950](https://www.rfc-editor.org/rfc/rfc7950.txt), most of which are -not implemented. - - - - - diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 523a51a2..8adf51a2 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -44,6 +44,7 @@ bindir = @bindir@ libdir = @libdir@ mandir = @mandir@ libexecdir = @libexecdir@ +wwwdir = /www-data localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ includedir = @includedir@ diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index ef9af729..87167585 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -201,7 +201,6 @@ netconf_input_cb(int s, retval = 0; goto done; } - for (i=0; i +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include +#include + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + @note what is copyright of this? + */ +int +b64_decode(const char *src, + char *target, + size_t targsize) +{ + int tarindex, state, ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = ((pos - Base64) & 0x0f) + << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if ((size_t)tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = ((pos - Base64) & 0x03) + << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if ((size_t)tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + default: + return -1; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for ((void)NULL; ch != '\0'; ch = *src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} + + +/*! Process a rest request that requires (cookie) "authentication" + * Note, this is loaded as dlsym fixed symbol in plugin + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[out] username Malloced username, or NULL. + * @retval -1 Fatal error + * @retval 0 OK + * For grideye, return "u" entry name if it has a valid "user" entry. + */ +int +plugin_credentials(clicon_handle h, + FCGX_Request *r, + char **username) +{ + int retval = -1; + cxobj *xt = NULL; + cxobj *x; + char *xbody; + char *auth; + char *user = NULL; + char *passwd; + char *passwd2; + size_t authlen; + cbuf *cb = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + *username = NULL; /* unauthorized */ + /* Check if basic_auth set, if not return OK */ + if (clicon_rpc_get_config(h, "running", "/", NULL, &xt) < 0) + goto done; + if ((x = xpath_first(xt, "basic_auth")) == NULL) + goto none; + if ((xbody = xml_body(x)) == NULL) + goto none; + if (strcmp(xbody, "true")) + goto none; + /* At this point in the code we must use HTTP basic authentication */ + if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL) + goto done; + if (strlen(auth) < strlen("Basic ")) + goto fail; + if (strncmp("Basic ", auth, strlen("Basic "))) + goto fail; + auth += strlen("Basic "); + authlen = strlen(auth)*2; + if ((user = malloc(authlen)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(user, 0, authlen); + if ((ret = b64_decode(auth, user, authlen)) < 0) + goto done; + /* auth string is on the format user:passwd */ + if ((passwd = index(user,':')) == NULL) + goto fail; + *passwd = '\0'; + passwd++; + clicon_debug(1, "%s user:%s passwd:%s", __FUNCTION__, user, passwd); + if ((cb = cbuf_new()) == NULL) + goto done; + cprintf(cb, "auth[user=%s]", user); + if ((x = xpath_first(xt, cbuf_get(cb))) == NULL) + goto fail; + + passwd2 = xml_find_body(x, "password"); + if (strcmp(passwd, passwd2)) + goto fail; + if ((*username = strdup(user)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + fail: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (user) + free(user); + if (cb) + cbuf_free(cb); + if (xt) + xml_free(xt); + return retval; + none: /* basic_auth is not enabled, harcode authenticated user "none" */ + if ((*username = strdup("none")) == NULL){ + clicon_err(OE_XML, errno, "strdup"); + goto done; + } + goto fail; +} + + +/*! Restconf plugin initialization + */ +int +plugin_init(clicon_handle h) +{ + int retval = -1; + + clicon_debug(1, "%s restconf", __FUNCTION__); + retval = 0; + // done: + return retval; +} diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index f102de3d..48169df9 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -47,6 +47,10 @@ typedef void *plghndl_t; /* Find plugin by name callback. XXX Should be clicon internal */ typedef void *(find_plugin_t)(clicon_handle, char *); + + + + /* * Prototypes */ @@ -60,7 +64,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *); * @see plginit_t */ #define PLUGIN_INIT "plugin_init" -typedef int (plginit_t)(clicon_handle); /* Plugin Init */ +typedef void * (plginit_t)(clicon_handle); /* Clixon plugin Init */ /* Called when backend started with cmd-line arguments from daemon call. * @see plgstart_t @@ -86,6 +90,18 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ */ typedef int (plgcredentials_t)(clicon_handle, void *, char **username); + +/* grideye agent plugin init struct for the api + * Note: Implicit init function, see PLUGIN_INIT_FN_V2 + */ +struct clixon_plugin_api{ + plgcredentials_t *cp_auth; +}; + +/* + * Prototypes + */ + /* Find a function in global namespace or a plugin. XXX clicon internal */ void *clicon_find_func(clicon_handle h, char *plugin, char *func); diff --git a/test/lib.sh b/test/lib.sh index b6964e2e..971b1088 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -28,13 +28,17 @@ rm -rf $dir/* # error and exit, arg is optional extra errmsg err(){ - echo "Error in Test$testnr [$testname]:" + echo -e "\e[31m\nError in Test$testnr [$testname]:" if [ $# -gt 0 ]; then echo "Expected: $1" fi if [ $# -gt 1 ]; then echo "Received: $2" fi + echo -e "\e[0m:" + echo "$ret"| od -t c > $dir/clixon-ret + echo "$expect"| od -t c > $dir/clixon-expect + diff $dir/clixon-ret $dir/clixon-expect exit $testnr } @@ -43,7 +47,11 @@ new(){ testnr=`expr $testnr + 1` testname=$1 >&2 echo "Test$testnr [$1]" -# sleep 1 +} +new2(){ + testnr=`expr $testnr + 1` + testname=$1 + >&2 echo -n "Test$testnr [$1]" } # clixon tester. First arg is command and second is expected outcome @@ -82,34 +90,15 @@ expectfn(){ fi } -# Similar to expectfn, but checks for equality and not only match -expecteq2(){ +expecteq(){ ret=$1 expect=$2 - - # Match if both are empty string if [ -z "$ret" -a -z "$expect" ]; then return fi - if [ "$ret" != "$expect" ]; then - err "$expect" "$ret" - fi -} - -# Similar to expectfn, but checks for equality and not only match -expecteq(){ - cmd=$1 - expect=$2 - ret=$($cmd) - - if [ $? -ne 0 ]; then - err "wrong args" - fi - # Match if both are empty string - if [ -z "$ret" -a -z "$expect" ]; then - return - fi - if [ "$ret" != "$expect" ]; then + if [[ "$ret" = "$expect" ]]; then + echo + else err "$expect" "$ret" fi } diff --git a/test/test_auth.sh b/test/test_auth.sh new file mode 100755 index 00000000..92325864 --- /dev/null +++ b/test/test_auth.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Authentication and authorization + +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang + +cat < $cfg + + $cfg + /usr/local/share/routing/yang + example + /usr/local/lib/routing/clispec + /usr/local/lib/routing/restconf + /usr/local/lib/routing/cli + routing + /usr/local/var/routing/routing.sock + /usr/local/var/routing/routing.pidfile + 1 + /usr/local/var/routing + /usr/local/lib/xmldb/text.so + false + +EOF + +cat < $fyang +module example{ + prefix ex; + leaf basic_auth{ + description "Basic user / password authentication as in HTTP basic auth"; + type boolean; + default false; + } + list auth { + description "user / password entries. Valid if basic_auth=true"; + key user; + leaf user{ + description "User name"; + type string; + } + leaf password{ + description "Password"; + type string; + } + } +} +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf + +new "start restconf daemon" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D + +sleep 1 + +new2 "auth get" +expecteq "$(curl -sS -X GET http://localhost/restconf/data)" '{"data": null} + ' + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "truefoobar]]>]]>" "^]]>]]>$" + +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" + +new2 "auth get (access denied)" +expecteq "$(curl -sS -X GET http://localhost/restconf/data)" "access-denied +The requested URL /restconf/data was unauthorized." + +new2 "auth get (access)" +expecteq "$(curl -u foo:bar -sS -X GET http://localhost/restconf/data)" '{"data": {"basic_auth": true,"auth": [{"user": "foo","password": "bar"}]}} + ' + +new "Kill restconf daemon" +sudo pkill -u www-data clixon_restconf + +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 7562a8f4..818388fb 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -78,22 +78,22 @@ sleep 1 new "restconf tests" -new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" -expecteq2 "$(curl -s -X GET http://localhost/.well-known/host-meta)" " +new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)" +expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" " " -new "restconf get restconf resource. RFC 8040 3.3 (json)" -expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}} +new2 "restconf get restconf resource. RFC 8040 3.3 (json)" +expecteq "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}} ' -new "restconf get restconf resource. RFC 8040 3.3 (xml)" +new2 "restconf get restconf resource. RFC 8040 3.3 (xml)" # Get XML instead of JSON? -expecteq2 "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 +expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '2016-06-21 ' -new "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}} +new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" @@ -104,8 +104,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get restconf/yang-library-version. RFC8040 3.3.3" -expecteq2 "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}' +new2 "restconf get restconf/yang-library-version. RFC8040 3.3.3" +expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}' new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version) @@ -122,12 +122,12 @@ new "restconf head. RFC 8040 4.2" expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" -new "restconf empty rpc" -expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null} +new2 "restconf empty rpc" +expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null} ' -new "restconf get empty config + state json" -expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +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}]}}} ' new "restconf get empty config + state xml" @@ -138,8 +138,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get data/interfaces-state/interface=eth0 json" -expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]} +new2 "restconf get data/interfaces-state/interface=eth0 json" +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]} ' new "restconf get state operation eth0 xml" @@ -151,8 +151,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf get state operation eth0 type json" -expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"} +new2 "restconf get state operation eth0 type json" +expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"} ' new "restconf get state operation eth0 type xml" @@ -164,8 +164,8 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi -new "restconf GET datastore" -expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +new2 "restconf GET datastore" +expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} ' # Exact match @@ -176,14 +176,14 @@ new "restconf Re-add subtree which should give error" expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' # XXX Cant get this to work -#expecteq2 "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' +#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}' new "restconf Check interfaces eth/0/0 added" expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}} ' -new "restconf delete interfaces" -expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" +new2 "restconf delete interfaces" +expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) "" new "restconf Check empty config" expectfn "curl -sG http://localhost/restconf/data" "$state" @@ -191,43 +191,43 @@ expectfn "curl -sG http://localhost/restconf/data" "$state" new "restconf Add interfaces subtree eth/0/0 using POST" expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' "" # XXX cant get this to work -#expecteq2 "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" +#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" "" -new "restconf Check eth/0/0 added" -expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +new2 "restconf Check eth/0/0 added" +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} ' -new "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 "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"}}} ' -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' "" +new2 "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" -new "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}]}}} +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}]}}} ' new "restconf delete eth/0/0" -expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "" +expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" "" new "Check deleted eth/0/0" expectfn 'curl -s -G http://localhost/restconf/data' $state -new "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 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"}}} ' -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 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)" "" -new "restconf get subtree" -expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} +new2 "restconf get subtree" +expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}} ' -new "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"}}}} +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"}}}} ' new "restconf rpc using POST xml" diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 37bb8708..7f8bdb7a 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -83,11 +83,11 @@ expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/rest new "restconf POST interface" expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' "" -new "restconf POST again" -expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +new2 "restconf POST again" +expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' -new "restconf POST from top" -expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' +new2 "restconf POST from top" +expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} ' new "restconf DELETE" expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' "" From b8e35742b97b1a134cc877dec0e111823fc45d9f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 25 Mar 2018 16:04:30 +0200 Subject: [PATCH 22/50] README update --- README.md | 38 +++++++++++++++++++------------------- doc/FAQ.md | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 301ceee8..04ef36c5 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ Topics * [Restconf](#restconf) * [Datastore](datastore/README.md) * [Authentication and Authorization](#auth) - * [Example](example/README.md) - * [Changelog](CHANGELOG.md) recent changes. - * [Clixon SDK](#SDK) + * [Example](example/) + * [Changelog](CHANGELOG.md) + * [Runtime](#runtime) * [Clicon and Clixon project page](http://www.clicon.org) - * [Tests](test/README.md) + * [Tests](test/) * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) Background @@ -37,8 +37,8 @@ Users of clixon currently include: * [Netgate](https://www.netgate.com) * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) - * [Netclean](https://www.netclean.com/solutions/whitebox) (only CLIgen) - * [Prosilient's PTAnalyzer] (only CLIgen) + * [Netclean](https://www.netclean.com/solutions/whitebox) # only CLIgen + * [Prosilient's PTAnalyzer](http://www.prosilient.com) # only CLIgen See also [Clicon project page](http://clicon.org). @@ -139,7 +139,7 @@ The following are not implemented - notifications (sec 6) - schema resource -See [more detailed restconf instructions](apps/restconf/README.md). +See [more detailed instructions](apps/restconf/README.md). Datastore ========= @@ -152,25 +152,25 @@ Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. O The datastore is primarily designed to be used by Clixon but can be used separately. -See [more detailed restconf instructions](datastore/README.md). - +See [more detailed instructions](datastore/README.md). Auth ==== -Authentication is not in-scope for Clixon, however, there is ongoing work -to implement [NACM](https://tools.ietf.org/html/rfc8341). +Authentication is managed outside Clixon using SSH, SSL, Oauth2, etc. -There are hooks (plugin callbacks) to identify which user is accessing a -client. That identity can then be used for authorization. +For CLI, login is typically made via SSH. For netconf, SSH netconf subsystem can be used. + +Restconf however needs credentials. This is done by writing a credentials callback in a restconf plugin. See: + * [FAQ](doc/FAQ.md#how-do-i-write-an-authentication-callback). + * [Example](example/README.md) has an example how to do this with HTTP basic auth. + * It would be possible for do this for more advanced mechanisms such as Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration) -In short, authentication needs to be coupled to clixon clients: - * CLI - Login has already been made via SSH - * Netconf - SSH netconf subsystem - * Restconf needs credentials. See [FAQ](doc/FAQ.md#How-do-I-write-an-authentication-callback). The [Example](example/README.md) has an example how to do this with HTTP basic auth. It is possible for do this for more advanced mechanisms such as Oauth2 or [https://github.com/CESNET/Netopeer2/tree/master/server/configuration] +There is an ongoing effort to implement authorization for Clixon according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at least a subset of the functionality. -SDK -=== + +Runtime +======= clixon sdk diff --git a/doc/FAQ.md b/doc/FAQ.md index 6603fbcd..1b90a6a0 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -320,5 +320,5 @@ If a plugin is provided, it needs to supply a username. If not, the request is unauthorized. the function mallocs a username and returns it. -See [../apps/example/routing_restconf.c] plugin_credentials() for +See (../apps/example/routing_restconf.c) plugin_credentials() for an example of HTTP basic auth. From 79e3fbdaa95393b07f06b66223b0a3b035192f1c Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 10:38:53 +0200 Subject: [PATCH 23/50] * Restructure and more generic plugin API (cli,backend,restconf,netconf) * For preparation for authorization RFC8341 * Plugins add clixon_plugin_init() and api struct for function pointers, eg: ``` static const struct clixon_plugin_api api = { "example", clixon_plugin_init, ... } clixon_plugin_api *clixon_plugin_init(clicon_handle h) { return (void*)&api; } ``` * Moved specific plugin functions from apps/ to generic functions in lib/ * New generic plugin load function: clixon_plugins_load() * Removed client-local netconf plugins netconf_plugin_callbacks() * This was code used before generic YANG rpc calls * Added username to clixon handle: * clicon_username_get() / clicon_username_set() * Added authentication plugin callback * Removed some obscure plugin code that seem not to be used (please report if needed!) * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() * Removed username to rpc calls (added below) --- CHANGELOG.md | 29 ++- README.md | 4 +- apps/backend/backend_plugin.c | 36 +--- apps/backend/clixon_backend_handle.h | 2 - apps/backend/clixon_backend_transaction.h | 1 - apps/cli/cli_common.c | 8 +- apps/cli/cli_plugin.c | 96 --------- apps/cli/cli_plugin.h | 5 +- apps/cli/cli_show.c | 6 +- apps/netconf/Makefile.in | 2 +- apps/netconf/netconf_main.c | 11 +- apps/netconf/netconf_plugin.c | 237 ---------------------- apps/netconf/netconf_plugin.h | 56 ----- apps/netconf/netconf_rpc.c | 9 +- apps/restconf/Makefile.in | 2 +- apps/restconf/clixon_restconf.h | 5 - apps/restconf/restconf_lib.c | 123 ----------- apps/restconf/restconf_lib.h | 5 - apps/restconf/restconf_main.c | 52 +++-- apps/restconf/restconf_methods.c | 40 ++-- apps/restconf/restconf_methods.h | 16 +- example/routing_cli.c | 2 +- example/routing_netconf.c | 31 +-- example/routing_restconf.c | 58 +++--- lib/clixon/clixon_handle.h | 3 + lib/clixon/clixon_options.h | 5 + lib/clixon/clixon_plugin.h | 78 +++++-- lib/clixon/clixon_proto_client.h | 4 +- lib/src/clixon_handle.c | 32 +-- lib/src/clixon_options.c | 31 ++- lib/src/clixon_plugin.c | 211 ++++++++++++++++--- lib/src/clixon_proto_client.c | 17 +- lib/src/clixon_xml_db.c | 4 +- lib/src/clixon_xml_map.c | 8 +- lib/src/clixon_yang.c | 1 + lib/src/clixon_yang_type.c | 1 + test/lib.sh | 2 +- test/test_netconf.sh | 3 +- test/test_perf.sh | 2 +- test/test_restconf2.sh | 2 +- yang/clixon-config@2018-02-12.yang | 2 +- 41 files changed, 470 insertions(+), 772 deletions(-) delete mode 100644 apps/netconf/netconf_plugin.c delete mode 100644 apps/netconf/netconf_plugin.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c08b5de..fc05764f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ ### Major changes: +* Restructure and more generic plugin API (cli,backend,restconf,netconf) + * For preparation for authorization RFC8341 + * Plugins add clixon_plugin_init() and api struct for function pointers, eg: +``` +static const struct clixon_plugin_api api = { + "example", + clixon_plugin_init, + ... +} +clixon_plugin_api *clixon_plugin_init(clicon_handle h) +{ + return (void*)&api; +} +``` + * Moved specific plugin functions from apps/ to generic functions in lib/ + * New generic plugin load function: clixon_plugins_load() + * Removed client-local netconf plugins netconf_plugin_callbacks() + * This was code used before generic YANG rpc calls + * Added username to clixon handle: + * clicon_username_get() / clicon_username_set() + * Added authentication plugin callback + * Removed some obscure plugin code that seem not to be used (please report if needed!) + * CLI parse hook + * CLICON_FIND_PLUGIN + * clicon_valcb() + * Added Clixon Restconf library * Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h * The restconf library can be included by a restconf plugin. @@ -20,6 +46,7 @@ ### Minor changes: +* Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. * Add username to rpc calls to prepare for authorization for backend: @@ -92,7 +119,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * New CLICON_XML_SORT configuration option. Default is true. Disable by setting to false. * Added yang ordered-by user. The default (ordered-by system) will now sort lists and leaf-lists alphabetically to increase search performance. Note that this may change outputs. * If you need legacy order, either set CLICON_XML_SORT to false, or set that list to "ordered-by user". - * This replaces XML hash experimental code, ie xml_child_hash variables and all xml_hash_ functions have been removed. + * This replaces XML hash experimental code, ie xml_child_hash variables and all xmlv_hash_ functions have been removed. * Implementation detail: Cached keys are stored in in yang Y_LIST nodes as cligen vector, see ys_populate_list() * Datastore cache introduced: cache XML tree in memory for faster get access. diff --git a/README.md b/README.md index 04ef36c5..6f4e3ee3 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ Clixon is an automatic configuration manager where you generate interactive CLI, NETCONF, RESTCONF and embedded databases with transaction support from a YANG specification. -Topics -====== * [Background](#background) * [Frequently asked questions](doc/FAQ.md) * [Installation](#installation) @@ -38,7 +36,7 @@ Users of clixon currently include: * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) * [Netclean](https://www.netclean.com/solutions/whitebox) # only CLIgen - * [Prosilient's PTAnalyzer](http://www.prosilient.com) # only CLIgen + * [Prosilient's PTAnalyzer](https://prosilient.com) # only CLIgen See also [Clicon project page](http://clicon.org). diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index fe3e5a6a..ffe76941 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -71,7 +71,7 @@ * @note the following should match the prototypes in clixon_backend.h */ #define PLUGIN_RESET "plugin_reset" -typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system status */ + /*! Plugin callback, if defined called to get state data from plugin * @param[in] h Clicon handle @@ -82,7 +82,7 @@ typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system statu * @see xmldb_get */ #define PLUGIN_STATEDATA "plugin_statedata" -typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); + #define PLUGIN_TRANS_BEGIN "transaction_begin" #define PLUGIN_TRANS_VALIDATE "transaction_validate" @@ -92,8 +92,6 @@ typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); #define PLUGIN_TRANS_ABORT "transaction_abort" -typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */ - /* Backend (config) plugins */ struct plugin { char p_name[PATH_MAX]; /* Plugin name */ @@ -118,28 +116,6 @@ struct plugin { static int _nplugins = 0; static struct plugin *_plugins = NULL; -/*! Find a plugin by name and return the dlsym handl - * Used by libclicon code to find callback funcctions in plugins. - * @param[in] h Clicon handle - * @param[in] h Name of plugin - * @retval handle Plugin handle if found - * @retval NULL Not found - */ -static void * -config_find_plugin(clicon_handle h, - char *name) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (strcmp(p->p_name, name) == 0) - return p->p_handle; - } - return NULL; -} - /*! Initialize plugin code (not the plugins themselves) * @param[in] h Clicon handle * @retval 0 OK @@ -148,14 +124,6 @@ config_find_plugin(clicon_handle h, int backend_plugin_init(clicon_handle h) { - find_plugin_t *fp = config_find_plugin; - clicon_hash_t *data = clicon_data(h); - - /* Register CLICON_FIND_PLUGIN in data hash */ - if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) { - clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN"); - return -1; - } return 0; } diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index 2edb61b8..fa596ed0 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -92,8 +92,6 @@ int subscription_delete(clicon_handle h, char *stream, struct handle_subscription *subscription_each(clicon_handle h, struct handle_subscription *hprev); -/* XXX backward compat */ -#define backend_netconf_register_callback(a,b,c,d) backend_rpc_cb_register(a,b,c,d) int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg, char *tag); diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index 2647a45c..1d2a523e 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -55,7 +55,6 @@ typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, * (defined in config_dbdep.c) * @see transaction_data_t internal structure */ -typedef void *transaction_data; uint64_t transaction_id(transaction_data td); void *transaction_arg(transaction_data td); cxobj *transaction_src(transaction_data td); diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index a4ec0130..4979f412 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -654,13 +654,13 @@ compare_dbs(clicon_handle h, astext = cv_int32_get(cvec_i(argv, 0)); else astext = 0; - if (clicon_rpc_get_config(h, "running", "/", NULL, &xc1) < 0) + if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0) goto done; if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } - if (clicon_rpc_get_config(h, "candidate", "/", NULL, &xc2) < 0) + if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0) goto done; if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -827,7 +827,7 @@ save_config_file(clicon_handle h, goto done; } filename = cv_string_get(cv); - if (clicon_rpc_get_config(h, dbstr,"/", NULL, &xt) < 0) + if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) goto done; if (xt == NULL){ clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */ @@ -1180,7 +1180,7 @@ cli_copy_config(clicon_handle h, cprintf(cb, xpath, keyname, fromname); /* Get from object configuration and store in x1 */ - if (clicon_rpc_get_config(h, db, cbuf_get(cb), NULL, &x1) < 0) + if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0) goto done; if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index dd399950..bc7f9904 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -66,13 +66,11 @@ #include "cli_plugin.h" #include "cli_handle.h" - /*! Name of master plugin functions * More in clicon_plugin.h * @note not really used consider documenting or remove */ #define PLUGIN_PROMPT_HOOK "plugin_prompt_hook" -#define PLUGIN_PARSE_HOOK "plugin_parse_hook" #define PLUGIN_SUSP_HOOK "plugin_susp_hook" /* @@ -380,7 +378,6 @@ cli_plugin_load_dir(clicon_handle h, struct stat st; int retval = -1; - /* Format master plugin path */ if ((master_plugin = clicon_master_plugin(h)) == NULL){ clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); @@ -403,8 +400,6 @@ cli_plugin_load_dir(clicon_handle h, /* Look up certain call-backs in master plugin */ stx->stx_prompt_hook = dlsym(cp->cp_handle, PLUGIN_PROMPT_HOOK); - stx->stx_parse_hook = - dlsym(cp->cp_handle, PLUGIN_PARSE_HOOK); stx->stx_susp_hook = dlsym(cp->cp_handle, PLUGIN_SUSP_HOOK); INSQ(cp, stx->stx_plugins); @@ -679,15 +674,6 @@ clicon_parse(clicon_handle h, goto done; case CG_NOMATCH: /* no match */ smode = NULL; - if (stx->stx_parse_hook) { - /* Try to find a match in upper modes, a'la IOS. */ - if ((modename = stx->stx_parse_hook(h, cmd, modename)) != NULL) { - if ((smode = syntax_mode_find(stx, modename, 0)) != NULL) - continue; - else - cli_output(f, "Can't find syntax mode '%s'\n", modename); - } - } /* clicon_err(OE_CFG, 0, "CLI syntax error: \"%s\": %s", cmd, cli_nomatch(h));*/ cli_output(f, "CLI syntax error: \"%s\": %s\n", @@ -745,42 +731,14 @@ clicon_cliread(clicon_handle h) return ret; } -/* - * cli_find_plugin - * Find a plugin by name and return the dlsym handl - * Used by libclicon code to find callback funcctions in plugins. - */ -static void * -cli_find_plugin(clicon_handle h, char *plugin) -{ - struct cli_plugin *p; - - p = plugin_find_cli(cli_syntax(h), plugin); - if (p) - return p->cp_handle; - - return NULL; -} - - /*! Initialize plugin code (not the plugins themselves) */ int cli_plugin_init(clicon_handle h) { - find_plugin_t *fp = cli_find_plugin; - clicon_hash_t *data = clicon_data(h); - - /* Register CLICON_FIND_PLUGIN in data hash */ - if (hash_add(data, "CLICON_FIND_PLUGIN", &fp, sizeof(fp)) == NULL) { - clicon_err(OE_UNIX, errno, "failed to register CLICON_FIND_PLUGIN"); - return -1; - } - return 0; } - /* * * CLI PLUGIN INTERFACE, PUBLIC SECTION @@ -816,7 +774,6 @@ cli_syntax_mode(clicon_handle h) return csm->csm_name; } - /* * Callback from cli_set_prompt(). Set prompt format for syntax mode * Arguments: @@ -880,7 +837,6 @@ prompt_fmt (char *prompt, size_t plen, char *fmt, ...) cprintf(cb, "%c", *s); s++; } - done: if (cb) fmt = cbuf_get(cb); @@ -905,55 +861,3 @@ cli_prompt(char *fmt) return prompt; } -/*! Find a cli plugin based on name and resolve a function pointer in it. - * Callback from clicon_dbvars_parse() - * Find a cli plugin based on name if given and use dlsym to resolve a - * function pointer in it. - * Call the resolved function to get the cgv populated - */ -int -clicon_valcb(void *arg, cvec *vars, cg_var *cgv, char *fname, cg_var *funcarg) -{ - char *func; - char *plgnam = NULL; - void *handle; - struct cli_plugin *p; - cli_valcb_t *cb; - clicon_handle h = (clicon_handle)arg; - - /* Make copy */ - if ((fname = strdup(fname)) == NULL) { - clicon_err(OE_UNIX, errno, "strdup"); - return -1; - } - - /* Extract plugin name if any */ - if ((func = strstr(fname, "::")) != NULL) { - *func = '\0'; - func += 2; - plgnam = fname; - } - else - func = fname; - - /* If we have specified a plugin name, find the handle to be used - * with dlsym() - */ - handle = NULL; - if (plgnam && (p = plugin_find_cli(cli_syntax(h), plgnam))) - handle = p->cp_handle; - - /* Look up function pointer */ - if ((cb = dlsym(handle, func)) == NULL) { - clicon_err(OE_UNIX, errno, "unable to find %s()", func); - free(fname); - return -1; - } - free(fname); - - if (cb(vars, cgv, funcarg) < 0) - return -1; - - return 0; -} - diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index a81f057b..081b3332 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -77,10 +77,9 @@ typedef struct { int stx_nplugins; /* Number of plugins */ struct cli_plugin *stx_plugins; /* List of plugins */ int stx_nmodes; /* Number of syntax modes */ - cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */ - cli_syntaxmode_t *stx_modes; /* List of syntax modes */ + cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */ + cli_syntaxmode_t *stx_modes; /* List of syntax modes */ cli_prompthook_t *stx_prompt_hook; /* Prompt hook */ - cli_parsehook_t *stx_parse_hook; /* Parse mode hook */ cli_susphook_t *stx_susp_hook; /* Ctrl-Z hook from getline() */ } cli_syntax_t; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 7106965f..a13296b8 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -156,7 +156,7 @@ expand_dbvar(void *h, goto done; /* XXX read whole configuration, why not send xpath? */ - if (clicon_rpc_get_config(h, dbstr, "/", NULL, &xt) < 0) + if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -487,7 +487,7 @@ cli_show_config(clicon_handle h, else cprintf(cbxpath, "%s", xpath); /* Get configuration from database */ - if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), NULL, &xt) < 0) + if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); @@ -571,7 +571,7 @@ show_conf_xpath(clicon_handle h, } cv = cvec_find_var(cvv, "xpath"); xpath = cv_string_get(cv); - if (clicon_rpc_get_config(h, str, xpath, NULL, &xt) < 0) + if (clicon_rpc_get_config(h, str, xpath, &xt) < 0) goto done; if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 8adf51a2..17c09072 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -74,7 +74,7 @@ MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) -LIBSRC = netconf_hello.c netconf_rpc.c netconf_filter.c netconf_lib.c netconf_plugin.c +LIBSRC = netconf_hello.c netconf_rpc.c netconf_filter.c netconf_lib.c LIBOBJS = $(LIBSRC:.c=.o) all: $(MYLIB) $(APPL) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index 87167585..2e5ba214 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -67,7 +67,6 @@ #include "clixon_netconf.h" #include "netconf_lib.h" #include "netconf_hello.h" -#include "netconf_plugin.h" #include "netconf_rpc.h" /* Command line options to be passed to getopt(3) */ @@ -307,6 +306,7 @@ main(int argc, int quiet = 0; clicon_handle h; int use_syslog; + char *dir; /* Defaults */ use_syslog = 0; @@ -383,13 +383,14 @@ main(int argc, goto done; /* Initialize plugins group */ - if (netconf_plugin_load(h) < 0) - goto done; + if ((dir = clicon_netconf_dir(h)) != NULL) + if (clixon_plugins_load(h, dir) < 0) + goto done; /* Call start function is all plugins before we go interactive */ tmp = *(argv-1); *(argv-1) = argv0; - netconf_plugin_start(h, argc+1, argv-1); + clixon_plugin_start(h, argc+1, argv-1); *(argv-1) = tmp; if (!quiet) @@ -401,7 +402,7 @@ main(int argc, if (event_loop() < 0) goto done; done: - netconf_plugin_unload(h); + clixon_plugin_unload(h); netconf_terminate(h); clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); diff --git a/apps/netconf/netconf_plugin.c b/apps/netconf/netconf_plugin.c deleted file mode 100644 index b601969f..00000000 --- a/apps/netconf/netconf_plugin.c +++ /dev/null @@ -1,237 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - * - * handling netconf plugins - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -/* clicon netconf*/ -#include "clixon_netconf.h" -#include "netconf_lib.h" -#include "netconf_plugin.h" - -/* Database dependency description */ -struct netconf_reg { - qelem_t nr_qelem; /* List header */ - netconf_cb_t nr_callback; /* Validation/Commit Callback */ - void *nr_arg; /* Application specific argument to cb */ - char *nr_tag; /* Xml tag when matched, callback called */ -}; -typedef struct netconf_reg netconf_reg_t; - -static int nplugins = 0; -static plghndl_t *plugins = NULL; -static netconf_reg_t *deps = NULL; - -/*! Load all plugins you can find in CLICON_NETCONF_DIR - */ -int -netconf_plugin_load(clicon_handle h) -{ - int retval = -1; - char *dir; - int ndp; - struct dirent *dp = NULL; - int i; - char filename[MAXPATHLEN]; - plghndl_t *handle; - - /* If no DIR defined, then dont load plugins */ - if ((dir = clicon_netconf_dir(h)) == NULL){ - retval = 0; - goto quit; - } - - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* Load all plugins */ - for (i = 0; i < ndp; i++) { - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", - (int)strlen(filename), filename); - if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) - goto quit; - if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - goto quit; - } - plugins[nplugins++] = handle; - } - retval = 0; -quit: - if (dp) - free(dp); - return retval; -} - -/*! Unload all netconf plugins */ -int -netconf_plugin_unload(clicon_handle h) -{ - int i; - netconf_reg_t *nr; - - while((nr = deps) != NULL) { - DELQ(nr, deps, netconf_reg_t *); - if (nr->nr_tag) - free(nr->nr_tag); - free(nr); - } - for (i = 0; i < nplugins; i++) - plugin_unload(h, plugins[i]); - if (plugins){ - free(plugins); - plugins = NULL; - } - nplugins = 0; - return 0; -} - -/*! Call plugin_start in all plugins - */ -int -netconf_plugin_start(clicon_handle h, int argc, char **argv) -{ - int i; - plgstart_t *startfn; - - for (i = 0; i < nplugins; i++) { - /* Call exit function is it exists */ - if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL) - break; - optind = 0; - if (startfn(h, argc, argv) < 0) { - clicon_debug(1, "plugin_start() failed\n"); - return -1; - } - } - return 0; -} - - -/*! Register netconf callback - * Called from plugin to register a callback for a specific netconf XML tag. - */ -int -netconf_register_callback(clicon_handle h, - netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag) /* Xml tag when callback is made */ -{ - netconf_reg_t *nr; - - if ((nr = malloc(sizeof(netconf_reg_t))) == NULL) { - clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); - goto catch; - } - memset (nr, 0, sizeof (*nr)); - nr->nr_callback = cb; - nr->nr_arg = arg; - nr->nr_tag = strdup(tag); /* strdup */ - INSQ(nr, deps); - return 0; -catch: - if (nr){ - if (nr->nr_tag) - free(nr->nr_tag); - free(nr); - } - return -1; -} - -/*! See if there is any callback registered for this tag - * - * Look for local (client-side) netconf plugins. This feature may no - * longer be necessary as generic RPC:s should be handled by backend. - * - * @param[in] h clicon handle - * @param[in] xn Sub-tree (under xorig) at child of rpc: . - * @param[out] xret Return XML, error or OK - * - * @retval -1 Error - * @retval 0 OK, not found handler. - * @retval 1 OK, handler called - */ -int -netconf_plugin_callbacks(clicon_handle h, - cxobj *xn, - cxobj **xret) -{ - int retval = -1; - netconf_reg_t *nreg; - - if (deps != NULL){ - nreg = deps; - do { - if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){ - if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0) - goto done; - retval = 1; /* handled */ - goto done; - } - nreg = NEXTQ(netconf_reg_t *, nreg); - } while (nreg != deps); - } - retval = 0; - done: - return retval; -} - diff --git a/apps/netconf/netconf_plugin.h b/apps/netconf/netconf_plugin.h deleted file mode 100644 index 96cf2921..00000000 --- a/apps/netconf/netconf_plugin.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - * - * handling netconf plugins - *****************************************************************************/ -#ifndef _NETCONF_PLUGIN_H_ -#define _NETCONF_PLUGIN_H_ - -/* - * Types - */ - - -/* - * Prototypes - */ -int netconf_plugin_load(clicon_handle h); - -int netconf_plugin_start(clicon_handle h, int argc, char **argv); - -int netconf_plugin_unload(clicon_handle h); - -int netconf_plugin_callbacks(clicon_handle h, cxobj *xn, cxobj **xret); - -#endif /* _NETCONF_PLUGIN_H_ */ diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index a87a8bfb..8cc23e1b 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -65,7 +65,6 @@ #include "clixon_netconf.h" #include "netconf_lib.h" #include "netconf_filter.h" -#include "netconf_plugin.h" #include "netconf_rpc.h" /* @@ -1018,14 +1017,8 @@ netconf_rpc_dispatch(clicon_handle h, } /* Others */ else { - /* Look for local (client-side) netconf plugins. This feature may no - * longer be necessary as generic RPC:s should be handled by backend. - */ - if ((retval = netconf_plugin_callbacks(h, xe, xret)) < 0) + if ((retval = netconf_application_rpc(h, xe, xret)) < 0) goto done; - if (retval == 0) - if ((retval = netconf_application_rpc(h, xe, xret)) < 0) - goto done; if (retval == 0){ /* not handled by callback */ xml_parse_va(xret, NULL, "" "operation-failed" diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index b4b98057..1f7e3fff 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -62,7 +62,7 @@ LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB) LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB) -CPPFLAGS = @CPPFLAGS@ +CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index 06c2efc3..554458f3 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -60,11 +60,6 @@ int notimplemented(FCGX_Request *r); int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); - -int restconf_plugin_load(clicon_handle h); -int restconf_plugin_start(clicon_handle h, int argc, char **argv); -int restconf_plugin_unload(clicon_handle h); -int restconf_credentials(clicon_handle h, FCGX_Request *r, char **user); int get_user_cookie(char *cookiestr, char *attribute, char **val); diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 258a6d98..211a3e14 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -358,129 +358,6 @@ readdata(FCGX_Request *r) return cb; } - -static int nplugins = 0; -static plghndl_t *plugins = NULL; -static plgcredentials_t *_credentials_fn = NULL; /* Credentials callback */ - -/*! Load all plugins you can find in CLICON_RESTCONF_DIR - */ -int -restconf_plugin_load(clicon_handle h) -{ - int retval = -1; - char *dir; - int ndp; - struct dirent *dp = NULL; - int i; - plghndl_t *handle; - char filename[MAXPATHLEN]; - - clicon_debug(1, "%s", __FUNCTION__); - if ((dir = clicon_restconf_dir(h)) == NULL){ - retval = 0; - goto quit; - } - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - /* Load all plugins */ - for (i = 0; i < ndp; i++) { - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", - (int)strlen(filename), filename); - if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) - goto quit; - if ((_credentials_fn = dlsym(handle, PLUGIN_CREDENTIALS)) == NULL) - clicon_debug(1, "Failed to load %s", PLUGIN_CREDENTIALS); - else - clicon_debug(1, "%s callback loaded", PLUGIN_CREDENTIALS); - if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - goto quit; - } - plugins[nplugins++] = handle; - } - retval = 0; -quit: - if (dp) - free(dp); - return retval; -} - - -/*! Unload all restconf plugins */ -int -restconf_plugin_unload(clicon_handle h) -{ - int i; - - for (i = 0; i < nplugins; i++) - plugin_unload(h, plugins[i]); - if (plugins){ - free(plugins); - plugins = NULL; - } - nplugins = 0; - return 0; -} - -/*! Call plugin_start in all plugins - */ -int -restconf_plugin_start(clicon_handle h, - int argc, - char **argv) -{ - int i; - plgstart_t *startfn; - - for (i = 0; i < nplugins; i++) { - /* Call exit function is it exists */ - if ((startfn = dlsym(plugins[i], PLUGIN_START)) == NULL) - break; - optind = 0; - if (startfn(h, argc, argv) < 0) { - clicon_debug(1, "plugin_start() failed\n"); - return -1; - } - } - return 0; -} - -/*! Run the restconf user-defined credentials callback if present - * The callback is expected to return the authenticated user, or NULL if not - * authenticasted. - * If no callback exists, return user "none" - * @param[in] h Clicon handle - * @param[in] r Fastcgi request handle - * @param[out] user The authenticated user (or NULL). Malloced, must be freed. - */ -int -restconf_credentials(clicon_handle h, - FCGX_Request *r, - char **user) -{ - int retval = -1; - - clicon_debug(1, "%s", __FUNCTION__); - /* If no authentication callback then allow anything. Is this OK? */ - if (_credentials_fn == NULL){ - if ((*user = strdup("none")) == NULL){ - clicon_err(OE_XML, errno, "strdup"); - goto done; - } - goto ok; - } - if (_credentials_fn(h, r, user) < 0) - *user = NULL; - ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d user:%s", __FUNCTION__, retval, *user); - return retval; -} - /*! Parse a cookie string and return value of cookie attribute * @param[in] cookiestr cookie string according to rfc6265 (modified) * @param[in] attribute cookie attribute diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 59e381f4..8162ff9c 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -57,11 +57,6 @@ int notimplemented(FCGX_Request *r); int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); - -int restconf_plugin_load(clicon_handle h); -int restconf_plugin_start(clicon_handle h, int argc, char **argv); -int restconf_plugin_unload(clicon_handle h); -int restconf_credentials(clicon_handle h, FCGX_Request *r, char **user); int get_user_cookie(char *cookiestr, char *attribute, char **val); diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 309c5223..62f8b6f1 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -92,7 +92,6 @@ * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] dvec Stream input daat - * @param[in] username Authenticated user */ static int api_data(clicon_handle h, @@ -101,8 +100,7 @@ api_data(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data, - char *username) + char *data) { int retval = -1; char *request_method; @@ -127,17 +125,17 @@ api_data(clicon_handle h, if (strcmp(request_method, "OPTIONS")==0) retval = api_data_options(h, r); else if (strcmp(request_method, "HEAD")==0) - retval = api_data_head(h, r, pcvec, pi, qvec, username, pretty, use_xml); + retval = api_data_head(h, r, pcvec, pi, qvec, pretty, use_xml); else if (strcmp(request_method, "GET")==0) - retval = api_data_get(h, r, pcvec, pi, qvec, username, pretty, use_xml); + retval = api_data_get(h, r, pcvec, pi, qvec, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, username, pretty, use_xml, parse_xml); + retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PUT")==0) - retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, username, pretty, use_xml, parse_xml); + retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml); else if (strcmp(request_method, "PATCH")==0) - retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data, username); + retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data); else if (strcmp(request_method, "DELETE")==0) - retval = api_data_delete(h, r, api_path, pi, username, pretty, use_xml); + retval = api_data_delete(h, r, api_path, pi, pretty, use_xml); else retval = notfound(r); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -152,7 +150,6 @@ api_data(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user */ static int api_operations(clicon_handle h, @@ -161,8 +158,7 @@ api_operations(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data, - char *username) + char *data) { int retval = -1; char *request_method; @@ -185,9 +181,9 @@ api_operations(clicon_handle h, parse_xml++; if (strcmp(request_method, "GET")==0) - retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, username, pretty, use_xml); + retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, use_xml); else if (strcmp(request_method, "POST")==0) - retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, username, + retval = api_operations_post(h, r, path, pcvec, pi, qvec, data, pretty, use_xml, parse_xml); else retval = notfound(r); @@ -338,7 +334,7 @@ api_restconf(clicon_handle h, cvec *pcvec = NULL; /* for rest api */ cbuf *cb = NULL; char *data; - char *username = NULL; + int authenticated = 0; clicon_debug(1, "%s", __FUNCTION__); path = FCGX_GetParam("REQUEST_URI", r->envp); @@ -384,12 +380,14 @@ api_restconf(clicon_handle h, /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ - if (restconf_credentials(h, r, &username) < 0) + if ((authenticated = clixon_plugin_auth(h, r)) < 0) goto done; - clicon_debug(1, "%s username:%s", __FUNCTION__, username); - clicon_debug(1, "%s credentials ok username:%s (should be non-NULL)", - __FUNCTION__, username); - if (username == NULL){ + /* If set but no user, we set a dummy user */ + if (authenticated){ + if (clicon_username_get(h) == NULL) + clicon_username_set(h, "none"); + } + else{ unauthorized(r); goto ok; } @@ -398,11 +396,11 @@ api_restconf(clicon_handle h, goto done; } else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, r, path, pcvec, 2, qvec, data, username) < 0) + if (api_data(h, r, path, pcvec, 2, qvec, data) < 0) goto done; } else if (strcmp(method, "operations") == 0){ /* rpc */ - if (api_operations(h, r, path, pcvec, 2, qvec, data, username) < 0) + if (api_operations(h, r, path, pcvec, 2, qvec, data) < 0) goto done; } else if (strcmp(method, "test") == 0) @@ -423,8 +421,6 @@ api_restconf(clicon_handle h, cvec_free(pcvec); if (cb) cbuf_free(cb); - if (username) - free(username); return retval; } @@ -500,6 +496,7 @@ main(int argc, char *path; clicon_handle h; char *yangspec=NULL; + char *dir; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG); @@ -556,8 +553,9 @@ main(int argc, clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", yangspec); /* Initialize plugins group */ - if (restconf_plugin_load(h) < 0) - return -1; + if ((dir = clicon_restconf_dir(h)) != NULL) + if (clixon_plugins_load(h, clicon_restconf_dir(h)) < 0) + return -1; /* Parse yang database spec file */ if (yang_spec_main(h) == NULL) @@ -605,7 +603,7 @@ main(int argc, } retval = 0; done: - restconf_plugin_unload(h); + clixon_plugin_unload(h); restconf_terminate(h); return retval; } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index fde9c4dc..729d01c6 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -227,7 +227,6 @@ api_return_err(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML * @param[in] head If 1 is HEAD, otherwise GET @@ -254,7 +253,6 @@ api_data_get2(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *username, int pretty, int use_xml, int head) @@ -284,7 +282,7 @@ api_data_get2(clicon_handle h, } path = cbuf_get(cbpath); clicon_debug(1, "%s path:%s", __FUNCTION__, path); - if (clicon_rpc_get(h, path, username, &xret) < 0){ + if (clicon_rpc_get(h, path, &xret) < 0){ notfound(r); goto ok; } @@ -362,7 +360,6 @@ api_data_get2(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML * @@ -377,11 +374,10 @@ api_data_head(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *username, int pretty, int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, username, pretty, use_xml, 1); + return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 1); } /*! REST GET method @@ -391,7 +387,6 @@ api_data_head(clicon_handle h, * @param[in] pcvec Vector of path ie DOCUMENT_URI element * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML * @code @@ -416,11 +411,10 @@ api_data_get(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *username, int pretty, int use_xml) { - return api_data_get2(h, r, pcvec, pi, qvec, username, pretty, use_xml, 0); + return api_data_get2(h, r, pcvec, pi, qvec, pretty, use_xml, 0); } /*! Generic REST POST method @@ -431,7 +425,6 @@ api_data_get(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data @@ -464,7 +457,6 @@ api_data_post(clicon_handle h, int pi, cvec *qvec, char *data, - char *username, int pretty, int use_xml, int parse_xml) @@ -484,6 +476,7 @@ api_data_post(clicon_handle h, cxobj *xret = NULL; cxobj *xretcom = NULL; cxobj *xerr; + char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, @@ -501,7 +494,7 @@ api_data_post(clicon_handle h, xbot = xtop; /* For internal XML protocol: add username attribute for backend access control */ - if (username){ + if ((username = clicon_username_get(h)) != NULL){ if ((xu = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xu, CX_ATTR); @@ -645,7 +638,6 @@ match_list_keys(yang_stmt *y, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data @@ -670,7 +662,6 @@ api_data_put(clicon_handle h, int pi, cvec *qvec, char *data, - char *username, int pretty, int use_xml, int parse_xml) @@ -692,6 +683,7 @@ api_data_put(clicon_handle h, cxobj *xret = NULL; cxobj *xretcom = NULL; cxobj *xerr; + char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", __FUNCTION__, api_path0, data); @@ -709,7 +701,7 @@ api_data_put(clicon_handle h, xbot = xtop; /* For internal XML protocol: add username attribute for backend access control */ - if (username){ + if ((username = clicon_username_get(h)) != NULL){ if ((xu = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xu, CX_ATTR); @@ -824,7 +816,6 @@ api_data_put(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user * Netconf: (nc:operation="merge") * See RFC8040 Sec 4.6 */ @@ -835,8 +826,7 @@ api_data_patch(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data, - char *username) + char *data) { notimplemented(r); return 0; @@ -847,7 +837,6 @@ api_data_patch(clicon_handle h, * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) * @param[in] pi Offset, where path starts - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML * See RFC 8040 Sec 4.7 @@ -860,7 +849,6 @@ api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, - char *username, int pretty, int use_xml) { @@ -877,6 +865,7 @@ api_data_delete(clicon_handle h, cxobj *xret = NULL; cxobj *xretcom = NULL; cxobj *xerr; + char *username; clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -891,7 +880,7 @@ api_data_delete(clicon_handle h, xbot = xtop; /* For internal XML protocol: add username attribute for backend access control */ - if (username){ + if ((username = clicon_username_get(h)) != NULL){ if ((xu = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xu, CX_ATTR); @@ -955,7 +944,6 @@ api_data_delete(clicon_handle h, * @param[in] pi Offset, where path starts * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML * @@ -976,7 +964,6 @@ api_operations_get(clicon_handle h, int pi, cvec *qvec, char *data, - char *username, int pretty, int use_xml) { @@ -1047,7 +1034,6 @@ api_operations_get(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data - * @param[in] username Authenticated user * @param[in] pretty Set to 1 for pretty-printed xml/json output * @param[in] use_xml Set to 0 for JSON and 1 for XML for output data * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data @@ -1063,7 +1049,6 @@ api_operations_post(clicon_handle h, int pi, cvec *qvec, char *data, - char *username, int pretty, int use_xml, int parse_xml) @@ -1085,7 +1070,8 @@ api_operations_post(clicon_handle h, cxobj *xoutput; cxobj *x; cxobj *xa; - + char *username; + clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); @@ -1112,7 +1098,7 @@ api_operations_post(clicon_handle h, xbot = xtop; /* For internal XML protocol: add username attribute for backend access control */ - if (username){ + if ((username = clicon_username_get(h)) != NULL){ if ((xa = xml_new("username", xtop, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); diff --git a/apps/restconf/restconf_methods.h b/apps/restconf/restconf_methods.h index b482d9ce..11daaa26 100644 --- a/apps/restconf/restconf_methods.h +++ b/apps/restconf/restconf_methods.h @@ -46,31 +46,31 @@ */ int api_data_options(clicon_handle h, FCGX_Request *r); int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec, char *username, int pretty, int use_xml); + cvec *qvec, int pretty, int use_xml); int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi, - cvec *qvec, char *username, int pretty, int use_xml); + cvec *qvec, int pretty, int use_xml); int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data, char *username, + cvec *qvec, char *data, int pretty, int use_xml, int parse_xml); int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data, char *username, + cvec *qvec, char *data, int pretty, int use_xml, int parse_xml); int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi, - cvec *qvec, char *data, char *username); + cvec *qvec, char *data); int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi, - char *username, int pretty, int use_xml); + int pretty, int use_xml); int api_operations_get(clicon_handle h, FCGX_Request *r, char *path, - cvec *pcvec, int pi, cvec *qvec, char *data, char *username, + cvec *pcvec, int pi, cvec *qvec, char *data, int pretty, int use_xml); int api_operations_post(clicon_handle h, FCGX_Request *r, char *path, cvec *pcvec, int pi, cvec *qvec, char *data, - char *username, int pretty, int use_xml, int parse_xml); + int pretty, int use_xml, int parse_xml); #endif /* _RESTCONF_METHODS_H_ */ diff --git a/example/routing_cli.c b/example/routing_cli.c index 62e2bc37..8610bd01 100644 --- a/example/routing_cli.c +++ b/example/routing_cli.c @@ -81,7 +81,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) /* Show eth0 interfaces config using XPATH */ if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]", - NULL, &xret) < 0) + &xret) < 0) goto done; xml_print(stdout, xret); diff --git a/example/routing_netconf.c b/example/routing_netconf.c index 6ee613fe..65883e2c 100644 --- a/example/routing_netconf.c +++ b/example/routing_netconf.c @@ -46,18 +46,7 @@ #include #include - -/* - * Plugin initialization - */ -int -plugin_init(clicon_handle h) -{ - return 0; -} - -/* - * Plugin start +/*! Plugin start * Called once everything has been initialized, right before * the main event loop is entered. */ @@ -73,3 +62,21 @@ plugin_exit(clicon_handle h) return 0; } +clixon_plugin_api * clixon_plugin_init(clicon_handle h); + +static const struct clixon_plugin_api api = { + "example", + clixon_plugin_init, + plugin_start, + plugin_exit, + NULL +}; + +/*! Netconf plugin initialization + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + return (void*)&api; +} + diff --git a/example/routing_restconf.c b/example/routing_restconf.c index 5ecc475d..d3340a2f 100644 --- a/example/routing_restconf.c +++ b/example/routing_restconf.c @@ -187,15 +187,16 @@ b64_decode(const char *src, * @param[in] r Fastcgi request handle * @param[out] username Malloced username, or NULL. * @retval -1 Fatal error - * @retval 0 OK + * @retval 0 Unauth + * @retval 1 Auth * For grideye, return "u" entry name if it has a valid "user" entry. */ int plugin_credentials(clicon_handle h, - FCGX_Request *r, - char **username) + void *arg) { int retval = -1; + FCGX_Request *r = (FCGX_Request *)arg; cxobj *xt = NULL; cxobj *x; char *xbody; @@ -208,19 +209,18 @@ plugin_credentials(clicon_handle h, int ret; clicon_debug(1, "%s", __FUNCTION__); - *username = NULL; /* unauthorized */ /* Check if basic_auth set, if not return OK */ - if (clicon_rpc_get_config(h, "running", "/", NULL, &xt) < 0) + if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) goto done; if ((x = xpath_first(xt, "basic_auth")) == NULL) - goto none; + goto ok; if ((xbody = xml_body(x)) == NULL) - goto none; + goto ok; if (strcmp(xbody, "true")) - goto none; + goto ok; /* At this point in the code we must use HTTP basic authentication */ if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL) - goto done; + goto fail; if (strlen(auth) < strlen("Basic ")) goto fail; if (strncmp("Basic ", auth, strlen("Basic "))) @@ -245,17 +245,15 @@ plugin_credentials(clicon_handle h, cprintf(cb, "auth[user=%s]", user); if ((x = xpath_first(xt, cbuf_get(cb))) == NULL) goto fail; - passwd2 = xml_find_body(x, "password"); if (strcmp(passwd, passwd2)) goto fail; - if ((*username = strdup(user)) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); + retval = 1; + if (clicon_username_set(h, user) < 0) goto done; - } - fail: - retval = 0; - done: + ok: /* authenticated */ + retval = 1; + done: /* error */ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (user) free(user); @@ -264,24 +262,26 @@ plugin_credentials(clicon_handle h, if (xt) xml_free(xt); return retval; - none: /* basic_auth is not enabled, harcode authenticated user "none" */ - if ((*username = strdup("none")) == NULL){ - clicon_err(OE_XML, errno, "strdup"); - goto done; - } - goto fail; + fail: /* unauthenticated */ + retval = 0; + goto done; } +clixon_plugin_api * clixon_plugin_init(clicon_handle h); + +static const struct clixon_plugin_api api = { + "example", + clixon_plugin_init, + NULL, + NULL, + plugin_credentials, +}; /*! Restconf plugin initialization */ -int -plugin_init(clicon_handle h) +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) { - int retval = -1; - clicon_debug(1, "%s restconf", __FUNCTION__); - retval = 0; - // done: - return retval; + return (void*)&api; } diff --git a/lib/clixon/clixon_handle.h b/lib/clixon/clixon_handle.h index d56b31dd..897560c3 100644 --- a/lib/clixon/clixon_handle.h +++ b/lib/clixon/clixon_handle.h @@ -50,6 +50,9 @@ typedef struct {float a;} *clicon_handle; typedef void *clicon_handle; #endif +/* The dynamicically loadable plugin object handle (should be in clixon_plugin.h) */ +typedef void *plghndl_t; + /* * Prototypes */ diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 6727c157..490e152c 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -179,4 +179,9 @@ int clicon_xmldb_api_set(clicon_handle h, void *xa_api); void *clicon_xmldb_handle_get(clicon_handle h); int clicon_xmldb_handle_set(clicon_handle h, void *xh); +/**/ +/* Set and get authorized user name */ +char *clicon_username_get(clicon_handle h); +int clicon_username_set(clicon_handle h, void *username); + #endif /* _CLIXON_OPTIONS_H_ */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 48169df9..3c9fb28f 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -44,13 +44,6 @@ /* The dynamicically loadable plugin object handle */ typedef void *plghndl_t; -/* Find plugin by name callback. XXX Should be clicon internal */ -typedef void *(find_plugin_t)(clicon_handle, char *); - - - - - /* * Prototypes */ @@ -64,6 +57,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *); * @see plginit_t */ #define PLUGIN_INIT "plugin_init" + typedef void * (plginit_t)(clicon_handle); /* Clixon plugin Init */ /* Called when backend started with cmd-line arguments from daemon call. @@ -77,36 +71,86 @@ typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */ #define PLUGIN_EXIT "plugin_exit" typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ -/*! Called by restconf to ceck credentials and return username +/*! Called by restconf to check credentials and return username */ #define PLUGIN_CREDENTIALS "plugin_credentials" -/* Plugin credentials +/* Plugin authorization. Set username option (or not) * @param[in] Clicon handle * @param[in] void*, eg Fastcgihandle request restconf - * @param[out] username should be freed after use * @retval 0 if credentials OK * @retval -1 credentials not OK */ -typedef int (plgcredentials_t)(clicon_handle, void *, char **username); +typedef int (plgauth_t)(clicon_handle, void *); +typedef int (plgreset_t)(clicon_handle h, const char *db); /* Reset system status */ +typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); -/* grideye agent plugin init struct for the api - * Note: Implicit init function, see PLUGIN_INIT_FN_V2 +typedef void *transaction_data; + +/* Transaction callbacks */ +typedef int (trans_cb_t)(clicon_handle h, transaction_data td); + +/* plugin init struct for the api + * Note: Implicit init function */ +struct clixon_plugin_api; +typedef struct clixon_plugin_api* (plginit2_t)(clicon_handle); /* Clixon plugin Init */ + struct clixon_plugin_api{ - plgcredentials_t *cp_auth; + char ca_name[PATH_MAX]; /* Name of plugin */ + plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ + plgstart_t *ca_start; /* Plugin start */ + plgexit_t *ca_exit; /* Plugin exit */ + plgauth_t *ca_auth; /* Auth credentials */ }; +struct clixon_backend_api{ + char ca_name[PATH_MAX]; /* Name of plugin */ + plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ + plgstart_t *ca_start; /* Plugin start */ + plgexit_t *ca_exit; /* Plugin exit */ + plgauth_t *ca_auth; /* Auth credentials */ + /*--Above here common fields with clixon_netconf_api, clixon_restconf_api, + * etc ----------*/ + plgreset_t *ca_reset; /* Reset system status (backend only) */ + + plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */ + trans_cb_t *ca_trans_begin; /* Transaction start */ + trans_cb_t *ca_trans_validate; /* Transaction validation */ + trans_cb_t *ca_trans_complete; /* Transaction validation complete */ + trans_cb_t *ca_trans_commit; /* Transaction commit */ + trans_cb_t *ca_trans_end; /* Transaction completed */ + trans_cb_t *ca_trans_abort; /* Transaction aborted */ +}; +typedef struct clixon_plugin_api clixon_plugin_api; + +#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ +/*! Called when plugin loaded. Only mandadory callback. All others optional + * @see plginit_t + */ + +/* Internal plugin structure with dlopen() handle and plugin_api + */ +struct clixon_plugin{ + plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */ + struct clixon_plugin_api cp_api; +}; +typedef struct clixon_plugin clixon_plugin; + /* * Prototypes */ - -/* Find a function in global namespace or a plugin. XXX clicon internal */ -void *clicon_find_func(clicon_handle h, char *plugin, char *func); +int clixon_plugins_load(clicon_handle h, char *dir); plghndl_t plugin_load (clicon_handle h, char *file, int dlflags); int plugin_unload(clicon_handle h, plghndl_t *handle); +int clixon_plugin_unload(clicon_handle h); + +int clixon_plugin_start(clicon_handle h, int argc, char **argv); + +int clixon_plugin_auth(clicon_handle h, void *arg); + #endif /* _CLIXON_PLUGIN_H_ */ diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 7adccc68..ac917d77 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -45,14 +45,14 @@ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0, int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp); int clicon_rpc_generate_error(char *format, cxobj *xerr); -int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, char *username, cxobj **xret); +int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret); int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op, char *xml); int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2); int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_unlock(clicon_handle h, char *db); -int clicon_rpc_get(clicon_handle h, char *xpath, char *username, cxobj **xret); +int clicon_rpc_get(clicon_handle h, char *xpath, cxobj **xret); int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_validate(clicon_handle h, char *db); diff --git a/lib/src/clixon_handle.c b/lib/src/clixon_handle.c index beff1c41..edac1382 100644 --- a/lib/src/clixon_handle.c +++ b/lib/src/clixon_handle.c @@ -50,9 +50,9 @@ #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_log.h" #include "clixon_err.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #define CLICON_MAGIC 0x99aafabe @@ -61,15 +61,19 @@ /*! Internal structure of basic handle. Also header of all other handles. * @note If you change here, you must also change the structs below: - * @see struct cli_handle, struct backend_handle + * @see struct cli_handle + * @see struct backend_handle */ struct clicon_handle { - int ch_magic; /* magic (HDR) */ - clicon_hash_t *ch_copt; /* clicon option list (HDR) */ - clicon_hash_t *ch_data; /* internal clicon data (HDR) */ + int ch_magic; /* magic (HDR) */ + clicon_hash_t *ch_copt; /* clicon option list (HDR) */ + clicon_hash_t *ch_data; /* internal clicon data (HDR) */ }; /*! Internal call to allocate a CLICON handle. + * + * @param[in] size Size of handle (internal) struct. + * @retval h Clicon handle * * There may be different variants of handles with some common options. * So far the only common options is a MAGIC cookie for sanity checks and @@ -102,6 +106,7 @@ clicon_handle_init0(int size) /*! Basic CLICON init functions returning a handle for API access. * + * @retval h Clicon handle * This is the first call to CLICON basic API which returns a handle to be * used in the API functions. There are other clicon_init functions for more * elaborate applications (cli/backend/netconf). This should be used by the most @@ -114,6 +119,7 @@ clicon_handle_init(void) } /*! Deallocate clicon handle, including freeing handle data. + * @param[in] h Clicon handle * @Note: handle 'h' cannot be used in calls after this */ int @@ -131,9 +137,10 @@ clicon_handle_exit(clicon_handle h) return 0; } -/* - * Check struct magic number for sanity checks - * return 0 if OK, -1 if fail. +/*! Check struct magic number for sanity checks + * @param[in] h Clicon handle + * @retval 0 Sanity check OK + * @retval -1 Sanity check failed */ int clicon_handle_check(clicon_handle h) @@ -144,8 +151,8 @@ clicon_handle_check(clicon_handle h) return ch->ch_magic == CLICON_MAGIC ? 0 : -1; } -/* - * Return clicon options (hash-array) given a handle. +/*! Return clicon options (hash-array) given a handle. + * @param[in] h Clicon handle */ clicon_hash_t * clicon_options(clicon_handle h) @@ -155,8 +162,8 @@ clicon_options(clicon_handle h) return ch->ch_copt; } -/* - * Return clicon data (hash-array) given a handle. +/*! Return clicon data (hash-array) given a handle. + * @param[in] h Clicon handle */ clicon_hash_t * clicon_data(clicon_handle h) @@ -165,4 +172,3 @@ clicon_data(clicon_handle h) return ch->ch_data; } - diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 6c528662..43611c6a 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -64,9 +64,9 @@ #include "clixon_handle.h" #include "clixon_log.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_xml_map.h" @@ -715,3 +715,32 @@ clicon_xmldb_handle_set(clicon_handle h, } +/*! Get authorized user name + * @param[in] h Clicon handle + * @retval xh XMLDB storage handle. If not connected return NULL + */ +char * +clicon_username_get(clicon_handle h) +{ + clicon_hash_t *cdat = clicon_data(h); + + return (char*)hash_value(cdat, "username", NULL); +} + +/*! Set authorized user name + * @param[in] h Clicon handle + * @param[in] xh XMLDB storage handle. If NULL reset it + * @note Just keep note of it, dont allocate it or so. + */ +int +clicon_username_set(clicon_handle h, + void *username) +{ + clicon_hash_t *cdat = clicon_data(h); + + if (username == NULL) + return hash_del(cdat, "username"); + return hash_add(cdat, "username", username, strlen(username)+1)==NULL?-1:0; +} + + diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 39e78e7f..6a5f589e 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -38,52 +38,130 @@ #include #include +#include #include #include #include +#include + +#include +#include + +/* cligen */ +#include #include "clixon_err.h" #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_log.h" +#include "clixon_file.h" #include "clixon_handle.h" +#include "clixon_yang.h" +#include "clixon_xml.h" #include "clixon_plugin.h" +/* XXX The below should be placed in clixon handle when done */ +static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */ +static int _clixon_nplugins = 0; /* Number of plugins */ -static find_plugin_t * -clicon_find_plugin(clicon_handle h) +/*! Load a dynamic plugin object and call its init-function + * @param[in] h Clicon handle + * @param[in] file Which plugin to load + * @param[in] dlflags See man(3) dlopen + * @retval cp Clixon plugin structure + * @retval NULL Error + * @see clixon_plugins_load Load all plugins + */ +static clixon_plugin * +plugin_load_one(clicon_handle h, + char *file, + int dlflags) { - void *p; - find_plugin_t *fp = NULL; - clicon_hash_t *data = clicon_data(h); - - if ((p = hash_value(data, "CLICON_FIND_PLUGIN", NULL)) != NULL) - memcpy(&fp, p, sizeof(fp)); + char *error; + void *handle = NULL; + plginit2_t *initfn; + clixon_plugin_api *api = NULL; + clixon_plugin *cp = NULL; - return fp; + clicon_debug(1, "%s", __FUNCTION__); + dlerror(); /* Clear any existing error */ + if ((handle = dlopen (file, dlflags)) == NULL) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); + goto done; + } + /* call plugin_init() if defined */ + if ((initfn = dlsym(handle, CLIXON_PLUGIN_INIT)) == NULL){ + clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file); + goto err; + } + if ((error = (char*)dlerror()) != NULL) { + clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); + goto done; + } + if ((api = initfn(h)) == NULL) { + clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error", + file); + goto err; + } + if ((cp = (clixon_plugin *)malloc(sizeof(*cp))) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + cp->cp_handle = handle; + cp->cp_api = *api; + clicon_debug(1, "%s", __FUNCTION__); + done: + return cp; + err: + if (handle) + dlclose(handle); + return NULL; } - -/*! Return a function pointer based on name of plugin and function. - * If plugin is specified, ask daemon registered function to return - * the dlsym handle of the plugin. +/*! Load a set of plugin objects from a directory and and call their init-function + * @param[in] h Clicon handle + * @param[in] dir Directory. .so files in this dir will be loaded. + * @retval 0 OK + * @retval -1 Error */ -void * -clicon_find_func(clicon_handle h, char *plugin, char *func) +int +clixon_plugins_load(clicon_handle h, + char *dir) { - find_plugin_t *plgget; - void *dlhandle = NULL; + int retval = -1; + int ndp; + struct dirent *dp = NULL; + int i; + char filename[MAXPATHLEN]; + clixon_plugin *cp; - if (plugin) { - /* find clicon_plugin_get() in global namespace */ - if ((plgget = clicon_find_plugin(h)) == NULL) { - clicon_err(OE_UNIX, errno, "Specified plugin not supported"); - return NULL; + clicon_debug(1, "%s", __FUNCTION__); + /* Get plugin objects names from plugin directory */ + if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) + goto done; + /* Load all plugins */ + for (i = 0; i < ndp; i++) { + snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); + clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", + (int)strlen(filename), filename); + if ((cp = plugin_load_one(h, filename, RTLD_NOW)) == NULL) + goto done; + _clixon_nplugins++; + if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) { + clicon_err(OE_UNIX, errno, "realloc"); + goto done; } - dlhandle = plgget(h, plugin); + _clixon_plugins[_clixon_nplugins-1] = *cp; + free(cp); } - - return dlsym(dlhandle, func); + retval = 0; +done: + if (dp) + free(dp); + return retval; } /*! Load a dynamic plugin object and call its init-function @@ -91,6 +169,7 @@ clicon_find_func(clicon_handle h, char *plugin, char *func) * @param[in] h Clicon handle * @param[in] file Which plugin to load * @param[in] dlflags See man(3) dlopen + * @see plugin_load_one for netxgen, this is soon OBSOLETE */ plghndl_t plugin_load(clicon_handle h, @@ -103,14 +182,14 @@ plugin_load(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); dlerror(); /* Clear any existing error */ - if ((handle = dlopen (file, dlflags)) == NULL) { + if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); goto done; } /* call plugin_init() if defined */ if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){ - clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading restconf plugin %s", file); + clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading clixon plugin %s", file); goto err; } if ((error = (char*)dlerror()) != NULL) { @@ -158,3 +237,81 @@ plugin_unload(clicon_handle h, } return retval; } + +/*! Unload all plugins + * @param[in] h Clicon handle + */ +int +clixon_plugin_unload(clicon_handle h) +{ + clixon_plugin *cp; + int i; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + plugin_unload(h, cp->cp_handle); + } + if (_clixon_plugins){ + free(_clixon_plugins); + _clixon_plugins = NULL; + } + _clixon_nplugins = 0; + return 0; +} + +/*! Call plugin_start in all plugins + * @param[in] h Clicon handle + */ +int +clixon_plugin_start(clicon_handle h, + int argc, + char **argv) +{ + clixon_plugin *cp; + int i; + plgstart_t *startfn; /* Plugin start */ + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((startfn = cp->cp_api.ca_start) == NULL) + continue; + if (startfn(h, argc, argv) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + } + return 0; +} + +/*! Run the restconf user-defined credentials callback if present + * Find first authentication callback and call that, then return. + * The callback is to set the authenticated user + * @param[in] h Clicon handle + * @param[in] arg Argument, such as fastcgi handler for restconf + * @retval -1 Error + * @retval 0 Not authenticated + * @retval 1 Authenticated + * @note If authenticated either a callback was called and clicon_username_set() + * Or no callback was found. + */ +int +clixon_plugin_auth(clicon_handle h, + void *arg) +{ + clixon_plugin *cp; + int i; + plgauth_t *authfn; /* Plugin auth */ + int retval = 1; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((authfn = cp->cp_api.ca_auth) == NULL) + continue; + if ((retval = authfn(h, arg)) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + break; + } + return retval; +} diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index cb150c0d..7fd7e08f 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -60,9 +61,9 @@ #include "clixon_hash.h" #include "clixon_handle.h" #include "clixon_yang.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_proto.h" #include "clixon_err.h" @@ -236,14 +237,13 @@ clicon_rpc_generate_error(char *format, * @param[in] h CLICON handle * @param[in] db Name of database * @param[in] xpath XPath (or "") - * @param[in] username Authenticated user (extra attribute) * @param[out] xt XML tree. Free with xml_free. * Either or . * @retval 0 OK * @retval -1 Error, fatal or xml * @code * cxobj *xt = NULL; - * if (clicon_rpc_get_config(h, "running", "/", username, &xt) < 0) + * if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) * err; * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ * clicon_rpc_generate_error("", xerr); @@ -258,7 +258,6 @@ int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, - char *username, cxobj **xt) { int retval = -1; @@ -266,11 +265,12 @@ clicon_rpc_get_config(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xd; - + char *username; + if ((cb = cbuf_new()) == NULL) goto done; cprintf(cb, "<%s/>", db); if (xpath && strlen(xpath)) @@ -498,7 +498,6 @@ clicon_rpc_unlock(clicon_handle h, /*! Get database configuration and state data * @param[in] h CLICON handle * @param[in] xpath XPath (or "") - * @param[in] username Authenticated user (extra attribute) * @param[out] xt XML tree. Free with xml_free. * Either or . * @retval 0 OK @@ -519,7 +518,6 @@ clicon_rpc_unlock(clicon_handle h, int clicon_rpc_get(clicon_handle h, char *xpath, - char *username, cxobj **xt) { int retval = -1; @@ -527,11 +525,12 @@ clicon_rpc_get(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xd; + char *username; if ((cb = cbuf_new()) == NULL) goto done; cprintf(cb, ""); if (xpath && strlen(xpath)) diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 42e5c870..8702910d 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -43,9 +43,11 @@ #include #include #include +#include #include #include -#include +#include + /* cligen */ #include diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 577fd298..bf64d04d 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -61,10 +61,11 @@ #include #include #include -#include -#include #include #include +#include +#include +#include #include /* cligen */ @@ -79,9 +80,9 @@ #include "clixon_string.h" #include "clixon_yang.h" #include "clixon_yang_type.h" -#include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_xml.h" +#include "clixon_plugin.h" #include "clixon_xsl.h" #include "clixon_log.h" #include "clixon_err.h" @@ -833,7 +834,6 @@ yang2api_path_fmt(yang_stmt *ys, return retval; } - /*! Transform an xml key format and a vector of values to an XML key * Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey() * Example: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2ecae84b..c704134f 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -66,6 +66,7 @@ #include "clixon_file.h" #include "clixon_yang.h" #include "clixon_hash.h" +#include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_yang_type.h" diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 2494cf5c..1d7f57d4 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -63,6 +63,7 @@ #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_hash.h" +#include "clixon_xml.h" #include "clixon_plugin.h" #include "clixon_options.h" #include "clixon_yang.h" diff --git a/test/lib.sh b/test/lib.sh index 971b1088..6a6f0e0b 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -11,7 +11,7 @@ clixon_cli=clixon_cli # For memcheck / performance #clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" -# clixon_netconf="valgrind --tool=callgrind clixon_netconf +#clixon_netconf="valgrind --tool=callgrind clixon_netconf" clixon_netconf=clixon_netconf # How to run restconf stand-alone and using valgrind diff --git a/test/test_netconf.sh b/test/test_netconf.sh index dae18487..71578145 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -39,8 +39,7 @@ if [ $? -ne 0 ]; then err fi -new "netconf tests" - +new "netconf get-config" expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" diff --git a/test/test_perf.sh b/test/test_perf.sh index ece6a3b6..dcbaa95f 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -72,7 +72,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df $cfg -y $fyang +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D sleep 1 diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 7f8bdb7a..b31f662b 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -59,7 +59,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df $cfg +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D sleep 1 diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 277c204d..d710fae3 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -241,7 +241,7 @@ module clixon-config { type string; default "master"; description - "Name of master plugin (both frontend and backend). + "Name of master plugin (cli, netconf, restconf and backend). Master plugin has special callbacks for frontends. See clicon user manual for more info. (Obsolete?)"; } From 0e52e86c0fecbb4e70dd9eaa7d3cc6f29d9f8805 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 11:15:24 +0200 Subject: [PATCH 24/50] CHANGELOG formatting --- CHANGELOG.md | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc05764f..39e95e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,40 +4,36 @@ ### Major changes: -* Restructure and more generic plugin API (cli,backend,restconf,netconf) - * For preparation for authorization RFC8341 - * Plugins add clixon_plugin_init() and api struct for function pointers, eg: -``` -static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - ... -} -clixon_plugin_api *clixon_plugin_init(clicon_handle h) -{ - return (void*)&api; -} -``` +* Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 + * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. * Moved specific plugin functions from apps/ to generic functions in lib/ - * New generic plugin load function: clixon_plugins_load() - * Removed client-local netconf plugins netconf_plugin_callbacks() - * This was code used before generic YANG rpc calls - * Added username to clixon handle: - * clicon_username_get() / clicon_username_set() - * Added authentication plugin callback + * Added authentication plugin callback (ca_auth) + * Added clicon_username_get() / clicon_username_set() + * Authorization + * Example extended with authorization + * Test added with http basic authorization (test/test_auth.sh) + * Documentation in FAQ.md * Removed some obscure plugin code that seem not to be used (please report if needed!) + * Client-local netconf plugins netconf_plugin_callbacks() * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() + * Example of new plugin module: +``` + static const struct clixon_plugin_api api = { + "example", + clixon_plugin_init, + ... + } + clixon_plugin_api *clixon_plugin_init(clicon_handle h){ + return (void*)&api; + } +``` * Added Clixon Restconf library * Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h * The restconf library can be included by a restconf plugin. * Example code in example/Makefile.in and example/restconf_lib.c -* Authorization - * Example extended with authorization - * Test added with http basic authorization (test/test_auth.sh) - * Documentation in FAQ.md * Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). * Proper RFC 6241 Netconf error handling * New functions added in clixon_netconf_lib.[ch] From 6ed769e082051345f61617dcb9452c4b3c5f7681 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 13:18:47 +0200 Subject: [PATCH 25/50] The Clixon example has changed name from routing to example affecting all config files, plugins, tests, etc. --- CHANGELOG.md | 9 +++-- apps/restconf/README.md | 4 +- apps/restconf/restconf_main.c | 2 +- apps/restconf/restconf_methods.c | 2 +- datastore/datastore_client.c | 6 +-- develop.md | 8 ++-- doc/FAQ.md | 39 ++++++++++--------- example/Makefile.in | 18 ++++----- example/README.md | 33 +++++++++++++--- example/example.xml | 18 +++++++++ example/example.yang | 3 -- .../{routing_backend.c => example_backend.c} | 0 example/{routing_cli.c => example_cli.c} | 0 example/{routing_cli.cli => example_cli.cli} | 4 +- .../{routing_netconf.c => example_netconf.c} | 0 ...{routing_restconf.c => example_restconf.c} | 0 example/routing.conf.local | 17 -------- example/routing.xml | 18 --------- test/test_auth.sh | 21 +++++----- test/test_cli.sh | 17 ++++---- test/test_leafref.sh | 16 ++++---- test/test_netconf.sh | 22 +++++------ test/test_order.sh | 14 +++---- test/test_perf.sh | 8 ++-- test/test_restconf.sh | 20 +++++----- test/test_restconf2.sh | 6 +-- test/test_startup.sh | 26 ++++++------- test/test_type.sh | 16 ++++---- test/test_yang.sh | 22 +++++------ 29 files changed, 187 insertions(+), 182 deletions(-) create mode 100644 example/example.xml rename example/{routing_backend.c => example_backend.c} (100%) rename example/{routing_cli.c => example_cli.c} (100%) rename example/{routing_cli.cli => example_cli.cli} (98%) rename example/{routing_netconf.c => example_netconf.c} (100%) rename example/{routing_restconf.c => example_restconf.c} (100%) delete mode 100644 example/routing.conf.local delete mode 100644 example/routing.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e95e6f..487f9e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ ### Minor changes: +* The Clixon example has changed name from "routing" to "example" affecting all config files, plugins, tests, etc. * Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. @@ -175,11 +176,11 @@ SUNET for support, requests, debugging, bugfixes and proposed solutions. * In backward compatible mode both .xml and .conf works * For migration from old to new, a utility is clixon_cli -x to print new format. Run the command and save in configuration file with .xml suffix instead. ``` - > clixon_cli -f /usr/local/etc/routing.conf -1x + > clixon_cli -f /usr/local/etc/example.conf -1x - /usr/local/etc/routing.xml - /usr/local/share/routing/yang - /usr/local/lib/routing/backend + /usr/local/etc/example.xml + /usr/local/share/example/yang + /usr/local/lib/example/backend ... ``` diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 78104f3d..d6c69b89 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -20,7 +20,7 @@ sudo /etc/init.d nginx start Start clixon restconf daemon ``` -olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/routing.xml " -s /bin/sh www-data +olof@vandal> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data ``` Make restconf calls with curl @@ -56,7 +56,7 @@ curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enable Start the restconf fastcgi program with debug flag: ``` -sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml" -s /bin/sh www-data +sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml" -s /bin/sh www-data ``` Look at syslog: ``` diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 62f8b6f1..234a23cc 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 729d01c6..547aa3ec 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -39,7 +39,7 @@ * sudo apt-get install libfcgi-dev * gcc -o fastcgi fastcgi.c -lfcgi - * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/routing.xml " -s /bin/sh www-data + * sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data * This is the interface: * api/data/profile=/metric= PUT data:enable= diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 5a411420..bdcd4ac5 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -33,11 +33,11 @@ * Examples: -./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip get / +./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get / -sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' +sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' -sudo ./datastore_client -d candidate -b /usr/local/var/routing -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/routing/yang -m ietf-ip put merge / 'eth0true' +sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / 'eth0true' */ diff --git a/develop.md b/develop.md index c23f5314..4ff98354 100644 --- a/develop.md +++ b/develop.md @@ -69,11 +69,11 @@ EOF cat < /tmp/myconf.xml /tmp/myconf.xml - /usr/local/share/routing/yang + /usr/local/share/example/yang example - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - /usr/local/var/routing + /usr/local/var/example/example.sock + /usr/local/var/example/example.pidfile + /usr/local/var/example /usr/local/lib/xmldb/text.so EOF diff --git a/doc/FAQ.md b/doc/FAQ.md index 1b90a6a0..2b151e27 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -24,7 +24,7 @@ Clixon is written in C. The plugins are written in C. The CLI specification uses cligen (http://cligen.se) ## How to best understand Clixon? -Run the ietf yang routing example, in the example directory. +Run the Clixon example, in the example directory. ## How do you build and install Clixon (and the example)? Clixon: @@ -56,14 +56,14 @@ Build using 'make doc' and aim your browser at doc/html/index.html or use the web resource: http://clicon.org/ref/index.html ## How do you run the example? -- Start a backend server: 'clixon_backend -Ff /usr/local/etc/routing.xml' -- Start a cli session: clixon_cli -f /usr/local/etc/routing.xml -- Start a netconf session: clixon_netconf -f /usr/local/etc/routing.xml +- Start a backend server: 'clixon_backend -Ff /usr/local/etc/example.xml' +- Start a cli session: clixon_cli -f /usr/local/etc/example.xml +- Start a netconf session: clixon_netconf -f /usr/local/etc/example.xml ## How is configuration data stored? Configuration data is stored in an XML datastore. The default is a text-based datastore. In the example the datastore are regular files found in -/usr/local/var/routing/. +/usr/local/var/example/. ## What is validate and commit? Clixon follows netconf in its validate and commit semantics. @@ -79,7 +79,7 @@ is the core functionality of a clixon system. Clixon options are stored in an XML configuration file. The default configuration file is /usr/local/etc/clixon.xml. The example -configuration file is installed at /usr/local/etc/routing.xml. The +configuration file is installed at /usr/local/etc/example.xml. The YANG specification for the configuration file is clixon-config.yang. You can change where CLixon looks for the configuration FILE as follows: @@ -109,12 +109,12 @@ You may also push the containers with 'make push' but you may then consider chan As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application. Example: - echo "]]>]]>" | clixon_netconf -f /usr/local/etc/routing.xml + echo "]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml However, more useful is to run clixon_netconf as an SSH subsystem. Register the subsystem in /etc/sshd_config: ``` - Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/routing.xml + Subsystem netconf /usr/local/bin/clixon_netconf -f /usr/local/etc/example.xml ``` and then invoke it from a client using ``` @@ -149,7 +149,7 @@ cli> ``` or via netconf: ``` -clixon_netconf -qf /usr/local/etc/routing.xml +clixon_netconf -qf /usr/local/etc/example.xml ROUTING]]>]]> ]]>]]> Routing notification]]>]]> @@ -194,18 +194,19 @@ clixon_backend ... -c extra.xml ``` The second way is by programming the plugin_reset() in the backend -plugin. The example code contains an example on how to do this (see plugin_reset() in routing_backend.c). +plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). ## I want to program. How do I extend the example? -- routing.xml - Change the configuration file +- example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. -- routing_cli.cli - Change the fixed part of the CLI commands -- routing_cli.c - Cli C-commands are placed here. -- routing_backend.c - Commit and validate functions. -- routing_netconf.c - Modify semantics of netconf commands. +- example_cli.cli - Change the fixed part of the CLI commands +- example_cli.c - Cli C-commands are placed here. +- example_backend.c - Commit and validate functions. +- example_netconf.c - Netconf plugin +- example_restconf.c - Add restconf authentication, etc. ## How do I write a commit function? -In the example, you write a commit function in routing_backend.c. +In the example, you write a commit function in example_backend.c. Every time a commit is made, transaction_commit() is called in the backend. It has a 'transaction_data td' argument which is used to fetch information on added, deleted and changed entries. You access this @@ -235,9 +236,9 @@ More are found in the doxygen reference. ## How do I write a CLI callback function? -1. You add an entry in routing_cli.cli +1. You add an entry in example_cli.cli > example("This is a comment") ("This is a variable"), mycallback("myarg"); -2. Then define a function in routing_cli.c +2. Then define a function in example_cli.c > mycallback(clicon_handle h, cvec *cvv, cvec *arv) where 'cvv' contains the value of the variable 'var' and 'argv' contains the string "myarg". @@ -320,5 +321,5 @@ If a plugin is provided, it needs to supply a username. If not, the request is unauthorized. the function mallocs a username and returns it. -See (../apps/example/routing_restconf.c) plugin_credentials() for +See (../apps/example/example_restconf.c) plugin_credentials() for an example of HTTP basic auth. diff --git a/example/Makefile.in b/example/Makefile.in index 12570d86..65df832f 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -38,7 +38,7 @@ bindir = @bindir@ includedir = @includedir@ datarootdir = @datarootdir@ -APPNAME = routing +APPNAME = example CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC @@ -53,13 +53,12 @@ PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) all: $(PLUGINS) -# Note: clixon.mk has a rule for: -# $(APPNAME.conf) +# Note: clixon.mk has rules for clixon_DBSPECDIR, clixon_SYSCONFDIR, etc used below -include $(DESTDIR)$(datarootdir)/clixon/clixon.mk -CLISPECS = routing_cli.cli +CLISPECS = $(APPNAME)_cli.cli -#YANGSPECS = $(APPNAME).yang +YANGSPECS = $(APPNAME).yang YANGSPECS += ietf-yang-types@2013-07-15.yang YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-interfaces@2014-05-08.yang @@ -68,28 +67,27 @@ YANGSPECS += ietf-routing@2014-10-26.yang YANGSPECS += ietf-ipv4-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipv6-unicast-routing@2014-10-26.yang YANGSPECS += ietf-ipsec@2016-03-09.yang -YANGSPECS += example.yang # Backend plugin -BE_SRC = routing_backend.c +BE_SRC = $(APPNAME)_backend.c BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) $(CC) -Wall -shared -o $@ -lc $< # CLI frontend plugin -CLI_SRC = routing_cli.c +CLI_SRC = $(APPNAME)_cli.c CLI_OBJ = $(CLI_SRC:%.c=%.o) $(CLI_PLUGIN): $(CLI_OBJ) $(CC) -Wall -shared -o $@ -lc $^ # NETCONF frontend plugin -NETCONF_SRC = routing_netconf.c +NETCONF_SRC = $(APPNAME)_netconf.c NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o) $(NETCONF_PLUGIN): $(NETCONF_OBJ) $(CC) -Wall -shared -o $@ -lc $^ # RESTCONF frontend plugin -RESTCONF_SRC = routing_restconf.c +RESTCONF_SRC = $(APPNAME)_restconf.c RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o) $(RESTCONF_PLUGIN): $(RESTCONF_OBJ) $(CC) -Wall -shared -o $@ -lc $^ diff --git a/example/README.md b/example/README.md index e2cf9fe6..27fd33d6 100644 --- a/example/README.md +++ b/example/README.md @@ -1,4 +1,19 @@ -# Clixon yang routing example +# Clixon example + +This directory contains a Clixon example which includes a simple +routing example. It contains the following files: +* example.xml The configuration file. See yang/clixon-config@.yang for all available fields. +* example.yang The yang spec of the example. It mainly includes ietf routing and IP modules. +* example_cli.cli CLIgen specification. +* example_cli.c CLI callback plugin containing functions called in the cli file above: a generic callback (`mycallback`) and an RPC (`fib_route_rpc`). +* example_backend.c Backend callback plugin including example of: + * transaction callbacks (validate/commit), + * notification, + * rpc handler + * state-data handler, ie non-config data +* example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback +* example_netconf.c Netconf callback plugin +* Makefile.in Example makefile where plugins are built and installed ## Compile and run ``` @@ -7,15 +22,23 @@ ``` Start backend: ``` - clixon_backend -f /usr/local/etc/routing.xml -I + clixon_backend -f /usr/local/etc/example.xml -I ``` Edit cli: ``` - clixon_cli -f /usr/local/etc/routing.xml + clixon_cli -f /usr/local/etc/example.xml ``` Send netconf command: ``` - clixon_netconf -f /usr/local/etc/routing.xml + clixon_netconf -f /usr/local/etc/example.xml +``` +Start clixon restconf daemon +``` +> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data +``` +Send restconf command +``` + curl -G http://127.0.0.1/restconf/data ``` ## Setting data example using netconf @@ -114,7 +137,7 @@ plugin_init(clicon_handle h) Netconf and restconf GET also returns state data, in contrast to config data. - +p In YANG state data is specified with "config false;". In the example, interface-state is state data. To return state data, you need to write a backend state data callback diff --git a/example/example.xml b/example/example.xml new file mode 100644 index 00000000..e93b840a --- /dev/null +++ b/example/example.xml @@ -0,0 +1,18 @@ + + /usr/local/etc/example.xml + /usr/local/share/example/yang + example + example + /usr/local/lib/example/backend + /usr/local/lib/example/netconf + /usr/local/lib/example/restconf + /usr/local/lib/example/cli + /usr/local/lib/example/clispec + /usr/local/var/example/example.sock + /usr/local/var/example/example.pidfile + 1 + /usr/local/var/example + /usr/local/lib/xmldb/text.so + 0 + init + diff --git a/example/example.yang b/example/example.yang index 831f30b3..eb4dbfdd 100644 --- a/example/example.yang +++ b/example/example.yang @@ -6,9 +6,6 @@ module example { import ietf-routing { prefix rt; } - import ietf-netconf-acm { - prefix nacm; /* See RFC 6536 */ - } description "Example code that includes ietf-ip and ietf-routing"; leaf basic_auth{ diff --git a/example/routing_backend.c b/example/example_backend.c similarity index 100% rename from example/routing_backend.c rename to example/example_backend.c diff --git a/example/routing_cli.c b/example/example_cli.c similarity index 100% rename from example/routing_cli.c rename to example/example_cli.c diff --git a/example/routing_cli.cli b/example/example_cli.cli similarity index 98% rename from example/routing_cli.cli rename to example/example_cli.cli index 63a285eb..6f8fdcfd 100644 --- a/example/routing_cli.cli +++ b/example/example_cli.cli @@ -1,7 +1,7 @@ # Common CLI syntax for both server and PMNode operatio mode -CLICON_MODE="routing"; +CLICON_MODE="example"; CLICON_PROMPT="%U@%H> "; -CLICON_PLUGIN="routing_cli"; +CLICON_PLUGIN="example_cli"; # Note, when switching to PT, change datamodel to only @datamodel set @datamodel:example, cli_set(); diff --git a/example/routing_netconf.c b/example/example_netconf.c similarity index 100% rename from example/routing_netconf.c rename to example/example_netconf.c diff --git a/example/routing_restconf.c b/example/example_restconf.c similarity index 100% rename from example/routing_restconf.c rename to example/example_restconf.c diff --git a/example/routing.conf.local b/example/routing.conf.local deleted file mode 100644 index a6caa01a..00000000 --- a/example/routing.conf.local +++ /dev/null @@ -1,17 +0,0 @@ -# Main YANG module first parsed by parser (in CLICON_YANG_DIR). eg clicon.yang. - -# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file -CLICON_CLI_MODE routing - -# Option used to construct initial yang file: -# [@] -#CLICON_YANG_MODULE_MAIN ietf-ip -CLICON_YANG_MODULE_MAIN example - -# Option used to construct initial yang file: -# [@] -#CLICON_YANG_MODULE_REVISION 2014-06-16 - -# Set to 0 if you want CLI to wrap to next line. -# Set to 1 if you want CLI to scroll sideways when approaching right margin -CLICON_CLI_LINESCROLLING 0 diff --git a/example/routing.xml b/example/routing.xml deleted file mode 100644 index 6ed38f96..00000000 --- a/example/routing.xml +++ /dev/null @@ -1,18 +0,0 @@ - - /usr/local/etc/routing.xml - /usr/local/share/routing/yang - example - routing - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - /usr/local/lib/routing/clispec - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile - 1 - /usr/local/var/routing - /usr/local/lib/xmldb/text.so - 0 - init - diff --git a/test/test_auth.sh b/test/test_auth.sh index 92325864..6ccf1d3c 100755 --- a/test/test_auth.sh +++ b/test/test_auth.sh @@ -1,6 +1,7 @@ #!/bin/bash # Authentication and authorization +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -11,23 +12,23 @@ fyangerr=$dir/err.yang cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so false EOF cat < $fyang -module example{ +module $APPNAME{ prefix ex; leaf basic_auth{ description "Basic user / password authentication as in HTTP basic auth"; diff --git a/test/test_cli.sh b/test/test_cli.sh index 496b9d95..338ba060 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -8,6 +8,7 @@ # Set the mandatory type # Commit +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -15,15 +16,15 @@ cfg=$dir/conf_yang.xml cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 789ca4f8..1b7c2990 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -1,6 +1,6 @@ #!/bin/bash # Test7: Yang specifics: leafref - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -9,15 +9,15 @@ fyang=$dir/leafref.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 71578145..f94b7226 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -1,6 +1,6 @@ #!/bin/bash # Test2: backend and netconf basic functionality - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -9,18 +9,18 @@ cfg=$dir/conf_yang.xml cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_order.sh b/test/test_order.sh index d29ad4f2..a046d5c6 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -5,7 +5,7 @@ # The ordered-by user MUST be the order it is entered. # No test of ordered-by system is done yet # (we may want to sort them alphabetically for better performance). - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_yang.xml @@ -25,13 +25,13 @@ fi cat < $cfg /tmp/conf_yang.xml - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 $dbdir /usr/local/lib/xmldb/text.so diff --git a/test/test_perf.sh b/test/test_perf.sh index dcbaa95f..8ad933e1 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -14,7 +14,7 @@ else echo "Usage: $0 [ []]" exit 1 fi - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -46,10 +46,10 @@ cat < $cfg $cfg $fyang ietf-ip - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile false - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 818388fb..13586009 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -1,7 +1,7 @@ #!/bin/bash # Restconf basic functionality # Assume http server setup, such as nginx described in apps/restconf/README.md - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf.xml @@ -11,18 +11,18 @@ fyang=$dir/restconf.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang $fyang - /usr/local/lib/routing/clispec - /usr/local/lib/routing/backend - /usr/local/lib/routing/restconf + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/restconf false - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index b31f662b..928defa4 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -1,7 +1,7 @@ #!/bin/bash # Restconf basic functionality # Assume http server setup, such as nginx described in apps/restconf/README.md - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf.xml @@ -14,9 +14,9 @@ cat < $cfg /usr/local/var $fyang false - /usr/local/var/routing/routing.sock + /usr/local/var/$APPNAME/$APPNAME.sock $dir/restconf.pidfile - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_startup.sh b/test/test_startup.sh index c81f88b9..d7b5d6a8 100755 --- a/test/test_startup.sh +++ b/test/test_startup.sh @@ -5,7 +5,7 @@ # - An extra xml configuration file starts with an "extra" interface # - running db starts with a "run" interface # - startup db starts with a "start" interface - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh cfg=$dir/conf_startup.xml @@ -13,18 +13,18 @@ cfg=$dir/conf_startup.xml cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - routing - /usr/local/lib/routing/backend - /usr/local/lib/routing/netconf - /usr/local/lib/routing/restconf - /usr/local/lib/routing/cli - /usr/local/lib/routing/clispec - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + $APPNAME + /usr/local/lib/$APPNAME/backend + /usr/local/lib/$APPNAME/netconf + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + /usr/local/lib/$APPNAME/clispec + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so 0 init @@ -48,7 +48,7 @@ run(){ EOF - sudo mv $dbdir /usr/local/var/routing/running_db + sudo mv $dbdir /usr/local/var/$APPNAME/running_db cat < $dbdir @@ -60,7 +60,7 @@ EOF EOF - sudo mv $dbdir /usr/local/var/routing/startup_db + sudo mv $dbdir /usr/local/var/$APPNAME/startup_db cat < $dir/config diff --git a/test/test_type.sh b/test/test_type.sh index e63bd05a..8eaaba92 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -1,7 +1,7 @@ #!/bin/bash # Advanced union types and generated code # and enum w values - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -12,15 +12,15 @@ fyang=$dir/type.yang cat < $cfg $cfg - /usr/local/share/routing/yang + /usr/local/share/$APPNAME/yang example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF diff --git a/test/test_yang.sh b/test/test_yang.sh index 4d5af03d..52d4a70f 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -1,6 +1,6 @@ #!/bin/bash # Test4: Yang specifics: multi-keys and empty type - +APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh @@ -11,21 +11,21 @@ fyangerr=$dir/err.yang cat < $cfg $cfg - /usr/local/share/routing/yang - example - /usr/local/lib/routing/clispec - /usr/local/lib/routing/cli - routing - /usr/local/var/routing/routing.sock - /usr/local/var/routing/routing.pidfile + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile 1 - /usr/local/var/routing + /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so EOF cat < $fyang -module example{ +module $APPNAME{ prefix ex; extension c-define { description "Example from RFC 6020"; @@ -87,7 +87,7 @@ EOF # This yang definition uses an extension which is not defined. Error when loading cat < $fyangerr -module example{ +module $APPNAME{ prefix ex; extension c-define { description "Example from RFC 6020"; From 7fbd95d491c6e5e69af380c7e1e0c5e28bf2bb05 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 13:33:24 +0200 Subject: [PATCH 26/50] Updated ietf-netconf-acm to ietf-netconf-acm@2018-02-14.yang from RFC 8341 --- CHANGELOG.md | 1 + yang/Makefile.in | 2 +- ....yang => ietf-netconf-acm@2018-02-14.yang} | 141 ++++++++++-------- 3 files changed, 83 insertions(+), 61 deletions(-) rename yang/{ietf-netconf-acm@2012-02-22.yang => ietf-netconf-acm@2018-02-14.yang} (73%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 487f9e5c..dc4dbe17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ ### Minor changes: +* Updated ietf-netconf-acm to ietf-netconf-acm@2018-02-14.yang from RFC 8341 * The Clixon example has changed name from "routing" to "example" affecting all config files, plugins, tests, etc. * Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. diff --git a/yang/Makefile.in b/yang/Makefile.in index cd3f988c..28a397c1 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -40,7 +40,7 @@ datarootdir = @datarootdir@ YANGSPECS = clixon-config@2018-02-12.yang YANGSPECS += ietf-netconf@2011-06-01.yang -YANGSPECS += ietf-netconf-acm@2012-02-22.yang +YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang APPNAME = clixon # subdir ehere these files are installed diff --git a/yang/ietf-netconf-acm@2012-02-22.yang b/yang/ietf-netconf-acm@2018-02-14.yang similarity index 73% rename from yang/ietf-netconf-acm@2012-02-22.yang rename to yang/ietf-netconf-acm@2018-02-14.yang index 32888d51..d6e7d86e 100644 --- a/yang/ietf-netconf-acm@2012-02-22.yang +++ b/yang/ietf-netconf-acm@2018-02-14.yang @@ -1,47 +1,54 @@ module ietf-netconf-acm { + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; - prefix "nacm"; + + prefix nacm; + import ietf-yang-types { prefix yang; } + organization "IETF NETCONF (Network Configuration) Working Group"; contact - "WG Web: + "WG Web: WG List: - WG Chair: Mehmet Ersue - - - WG Chair: Bert Wijnen - - - Editor: Andy Bierman + Author: Andy Bierman - Editor: Martin Bjorklund + Author: Martin Bjorklund "; description - "NETCONF Access Control Model. + "Network Configuration Access Control Model. - Copyright (c) 2012 IETF Trust and the persons identified as - authors of the code. All rights reserved. + Copyright (c) 2012 - 2018 IETF Trust and the persons + identified as authors of the code. All rights reserved. Redistribution and use in source and binary forms, with or without modification, is permitted pursuant to, and subject to the license terms contained in, the Simplified BSD License set forth in Section 4.c of the IETF Trust's Legal Provisions Relating to IETF Documents - (http://trustee.ietf.org/license-info). + (https://trustee.ietf.org/license-info). - This version of this YANG module is part of RFC 6536; see + This version of this YANG module is part of RFC 8341; see the RFC itself for full legal notices."; + revision "2018-02-14" { + description + "Added support for YANG 1.1 actions and notifications tied to + data nodes. Clarified how NACM extensions can be used by + other data models."; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + revision "2012-02-22" { description - "Initial version"; + "Initial version."; reference "RFC 6536: Network Configuration Protocol (NETCONF) Access Control Model"; @@ -56,11 +63,13 @@ module ietf-netconf-acm { "Used to indicate that the data model node represents a sensitive security system parameter. - If present, and the NACM module is enabled (i.e., - /nacm/enable-nacm object equals 'true'), the NETCONF server - will only allow the designated 'recovery session' to have - write access to the node. An explicit access control rule is - required for all other users. + If present, the NETCONF server will only allow the designated + 'recovery session' to have write access to the node. An + explicit access control rule is required for all other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. The 'default-deny-write' extension MAY appear within a data definition statement. It is ignored otherwise."; @@ -71,11 +80,14 @@ module ietf-netconf-acm { "Used to indicate that the data model node controls a very sensitive security system parameter. - If present, and the NACM module is enabled (i.e., - /nacm/enable-nacm object equals 'true'), the NETCONF server - will only allow the designated 'recovery session' to have - read, write, or execute access to the node. An explicit - access control rule is required for all other users. + If present, the NETCONF server will only allow the designated + 'recovery session' to have read, write, or execute access to + the node. An explicit access control rule is required for all + other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. The 'default-deny-all' extension MAY appear within a data definition statement, 'rpc' statement, or 'notification' @@ -91,12 +103,12 @@ module ietf-netconf-acm { length "1..max"; } description - "General Purpose Username string."; + "General-purpose username string."; } typedef matchall-string-type { type string { - pattern "\*"; + pattern '\*'; } description "The string containing a single asterisk '*' is used @@ -121,6 +133,7 @@ module ietf-netconf-acm { "Any protocol operation that alters an existing data node."; } + bit delete { description "Any protocol operation that removes a data node."; @@ -131,13 +144,13 @@ module ietf-netconf-acm { } } description - "NETCONF Access Operation."; + "Access operation."; } typedef group-name-type { type string { length "1..max"; - pattern "[^\*].*"; + pattern '[^\*].*'; } description "Name of administrative group to which @@ -164,29 +177,35 @@ module ietf-netconf-acm { type yang:xpath1.0; description "Path expression used to represent a special - data node instance identifier string. + data node, action, or notification instance-identifier + string. A node-instance-identifier value is an unrestricted YANG instance-identifier expression. - All the same rules as an instance-identifier apply - except predicates for keys are optional. If a key + + 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. - This XPath expression is evaluated in the following context: + This XML Path Language (XPath) expression is evaluated in the + following context: - o The set of namespace declarations are those in scope on - the leaf element where this type is used. + o The set of namespace declarations are those in scope on + the leaf element where this type is used. - o The set of variable bindings contains one variable, - 'USER', which contains the name of the user of the current - session. + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the + current session. - o The function library is the core function library, but - note that due to the syntax restrictions of an - instance-identifier, no functions are allowed. + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. - o The context node is the root node in the data tree."; + o The context node is the root node in the data tree. + + The accessible tree includes actions and notifications tied + to data nodes."; } /* @@ -197,11 +216,11 @@ module ietf-netconf-acm { nacm:default-deny-all; description - "Parameters for NETCONF Access Control Model."; + "Parameters for NETCONF access control model."; leaf enable-nacm { type boolean; - default true; + default "true"; description "Enables or disables all NETCONF access control enforcement. If 'true', then enforcement @@ -237,7 +256,7 @@ module ietf-netconf-acm { leaf enable-external-groups { type boolean; - default true; + default "true"; description "Controls whether the server uses the groups reported by the NETCONF transport layer when it assigns the user to a set of @@ -277,13 +296,13 @@ module ietf-netconf-acm { container groups { description - "NETCONF Access Control Groups."; + "NETCONF access control groups."; list group { key name; description - "One NACM Group Entry. This list will only contain + "One NACM group entry. This list will only contain configured entries, not any entries learned from any transport protocols."; @@ -304,7 +323,7 @@ module ietf-netconf-acm { } list rule-list { - key "name"; + key name; ordered-by user; description "An ordered collection of access control rules."; @@ -331,7 +350,7 @@ module ietf-netconf-acm { } list rule { - key "name"; + key name; ordered-by user; description "One access control rule. @@ -339,8 +358,8 @@ module ietf-netconf-acm { Rules are processed in user-defined order until a match is found. A rule matches if 'module-name', 'rule-type', and 'access-operations' match the request. If a rule - matches, the 'action' leaf determines if access is granted - or not."; + matches, the 'action' leaf determines whether or not + access is granted."; leaf name { type string { @@ -391,18 +410,20 @@ module ietf-netconf-acm { value equals the requested notification name."; } } + case data-node { leaf path { type node-instance-identifier; mandatory true; description - "Data Node Instance Identifier associated with the - data node controlled by this rule. + "Data node instance-identifier associated with the + data node, action, or notification controlled by + this rule. - Configuration data or state data instance - identifiers start with a top-level data node. A - complete instance identifier is required for this - type of path value. + Configuration data or state data + instance-identifiers start with a top-level + data node. A complete instance-identifier is + required for this type of path value. The special value '/' refers to all possible datastore contents."; @@ -428,7 +449,7 @@ module ietf-netconf-acm { mandatory true; description "The access control action associated with the - rule. If a rule is determined to match a + rule. If a rule has been determined to match a particular request, then this object is used to determine whether to permit or deny the request."; @@ -442,4 +463,4 @@ module ietf-netconf-acm { } } } -} \ No newline at end of file +} From b9a54f07f39eff1d0d91e5d7bd749c898cc0d5f7 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 2 Apr 2018 19:27:57 +0200 Subject: [PATCH 27/50] Restructure and more generic plugin API for backend --- CHANGELOG.md | 57 ++- README.md | 2 +- apps/backend/Makefile.in | 4 +- apps/backend/backend_client.c | 2 +- apps/backend/backend_main.c | 8 +- apps/backend/backend_plugin.c | 666 ++++++++-------------------- apps/backend/backend_plugin.h | 6 +- apps/netconf/netconf_main.c | 4 +- apps/restconf/restconf_main.c | 4 +- example/Makefile.in | 17 +- example/README.md | 1 + example/example_backend.c | 85 ++-- example/example_backend_secondary.c | 102 +++++ example/example_netconf.c | 18 +- example/example_restconf.c | 17 +- lib/clixon/clixon_plugin.h | 40 +- lib/src/clixon_file.c | 11 +- lib/src/clixon_plugin.c | 153 +++++-- lib/src/clixon_xml.c | 2 +- 19 files changed, 570 insertions(+), 629 deletions(-) create mode 100644 example/example_backend_secondary.c diff --git a/CHANGELOG.md b/CHANGELOG.md index dc4dbe17..b9ab88eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,28 +6,55 @@ ### Major changes: * Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. + * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. * Moved specific plugin functions from apps/ to generic functions in lib/ * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() - * Authorization - * Example extended with authorization - * Test added with http basic authorization (test/test_auth.sh) - * Documentation in FAQ.md * Removed some obscure plugin code that seem not to be used (please report if needed!) * Client-local netconf plugins netconf_plugin_callbacks() * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() - * Example of new plugin module: + * backend system plugins (CLIXON_BACKEND_SYSDIR) + * CLICON_MASTER_PLUGIN config variable + * Example of migrating a backend plugin module: + * Add all callbacks in a clixon_plugin_api struct + * Rename plugin_init() -> clixon_plugin_init() and return api as function value ``` - static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - ... - } - clixon_plugin_api *clixon_plugin_init(clicon_handle h){ - return (void*)&api; - } +/* This is old style with hardcoded function names (eg plugin_start) */ +int plugin_start(clicon_handle h, int argc, char **argv) +{ + return 0; +} +int +plugin_init(clicon_handle h) +{ + return 0; +} + +/* This is new style with all function names in api struct */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, + plugin_start, + plugin_exit, + NULL, /* auth N/A for backend */ + plugin_reset, + plugin_statedata, + transaction_begin, + transaction_validate, + transaction_complete, + transaction_commit, + transaction_end, + transaction_abort +}; + +clixon_plugin_api *clixon_plugin_init(clicon_handle h) +{ + return &api; /* Return NULL on error */ +} ``` * Added Clixon Restconf library @@ -42,8 +69,12 @@ ### Minor changes: +* Authentication + * Example extended with http basic authentication for restconf + * Documentation in FAQ.md * Updated ietf-netconf-acm to ietf-netconf-acm@2018-02-14.yang from RFC 8341 * The Clixon example has changed name from "routing" to "example" affecting all config files, plugins, tests, etc. + * Secondary backend plugin added * Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. diff --git a/README.md b/README.md index 6f4e3ee3..aff68c49 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Extending ========= Clixon provides a core system and can be used as-is using available Yang specifications. However, an application very quickly needs to -specialize funxtions. Clixon is extended by (most commonly) writing +specialize functions. Clixon is extended by (most commonly) writing plugins for cli and backend. Extensions for netconf and restconf are also available. diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 662b884b..585bc059 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -55,8 +55,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ # Use this clixon lib for linking CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) -# Location of system plugins -CLIXON_BACKEND_SYSDIR = $(libdir)/clixon/plugins/backend # For dependency. A little strange that we rely on it being built in the src dir # even though it may exist in $(libdir). But the new version may not have been installed yet. @@ -119,7 +117,7 @@ install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transac .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_BACKEND_SYSDIR=\"$(CLIXON_BACKEND_SYSDIR)\" $(CPPFLAGS) $(CFLAGS) -c $< + $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index a6ea354f..cf0bd2a5 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -289,7 +289,7 @@ from_client_get(clicon_handle h, /* Get state data from plugins as defined by plugin_statedata(), if any */ assert(xret); clicon_err_reset(); - if ((ret = backend_statedata_call(h, selector, xret)) < 0) + if ((ret = clixon_plugin_statedata(h, selector, xret)) < 0) goto done; if (ret == 0){ /* OK */ cprintf(cbret, ""); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index c8eb25eb..45c097c9 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -86,7 +86,7 @@ backend_terminate(clicon_handle h) clicon_debug(1, "%s", __FUNCTION__); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); - plugin_finish(h); + clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ backend_rpc_cb_delete_all(); if (pidfile) @@ -254,7 +254,7 @@ plugin_start_useroptions(clicon_handle h, tmp = *(argv-1); *(argv-1) = argv0; - if (plugin_start_argv(h, argc+1, argv-1) < 0) + if (clixon_plugin_start(h, argc+1, argv-1) < 0) return -1; *(argv-1) = tmp; return 0; @@ -370,7 +370,7 @@ startup_mode_running(clicon_handle h, if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) @@ -443,7 +443,7 @@ startup_mode_startup(clicon_handle h, if (db_reset(h, "tmp") < 0) goto done; /* Application may define extra xml in its reset function*/ - if (plugin_reset_state(h, "tmp") < 0) + if (clixon_plugin_reset(h, "tmp") < 0) goto done; /* Get application extra xml from file */ if (load_extraxml(h, extraxml_file, "tmp") < 0) diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index ffe76941..00025617 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -64,58 +64,6 @@ #include "backend_plugin.h" #include "backend_commit.h" -/* - * Types - */ -/* Following are specific to backend. For common see clicon_plugin.h - * @note the following should match the prototypes in clixon_backend.h - */ -#define PLUGIN_RESET "plugin_reset" - - -/*! Plugin callback, if defined called to get state data from plugin - * @param[in] h Clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in] xtop XML tree, on entry. - * @retval 0 OK - * @retval -1 Error - * @see xmldb_get - */ -#define PLUGIN_STATEDATA "plugin_statedata" - - -#define PLUGIN_TRANS_BEGIN "transaction_begin" -#define PLUGIN_TRANS_VALIDATE "transaction_validate" -#define PLUGIN_TRANS_COMPLETE "transaction_complete" -#define PLUGIN_TRANS_COMMIT "transaction_commit" -#define PLUGIN_TRANS_END "transaction_end" -#define PLUGIN_TRANS_ABORT "transaction_abort" - - -/* Backend (config) plugins */ -struct plugin { - char p_name[PATH_MAX]; /* Plugin name */ - void *p_handle; /* Dynamic object handle */ - plginit_t *p_init; /* Init */ - plgstart_t *p_start; /* Start */ - plgexit_t *p_exit; /* Exit */ - plgreset_t *p_reset; /* Reset state */ - plgstatedata_t *p_statedata; /* State-data callback */ - trans_cb_t *p_trans_begin; /* Transaction start */ - trans_cb_t *p_trans_validate; /* Transaction validation */ - trans_cb_t *p_trans_complete; /* Transaction validation complete */ - trans_cb_t *p_trans_commit; /* Transaction commit */ - trans_cb_t *p_trans_end; /* Transaction completed */ - trans_cb_t *p_trans_abort; /* Transaction aborted */ - -}; - -/* - * Local variables - */ -static int _nplugins = 0; -static struct plugin *_plugins = NULL; - /*! Initialize plugin code (not the plugins themselves) * @param[in] h Clicon handle * @retval 0 OK @@ -127,263 +75,6 @@ backend_plugin_init(clicon_handle h) return 0; } -/*! Unload a plugin - * @param[in] h Clicon handle - * @param[in] plg Plugin structure - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_unload(clicon_handle h, - struct plugin *plg) -{ - int retval=-1; - char *error; - - /* Call exit function is it exists */ - if (plg->p_exit) - plg->p_exit(h); - - dlerror(); /* Clear any existing error */ - if (dlclose(plg->p_handle) != 0) { - error = (char*)dlerror(); - clicon_err(OE_UNIX, 0, "dlclose: %s", error?error:"Unknown error"); - goto done; - /* Just report */ - } - else - clicon_debug(1, "Plugin '%s' unloaded.", plg->p_name); - retval = 0; - done: - return retval; -} - - -/*! Load a dynamic plugin and call its init-function - * @param[in] h Clicon handle - * @param[in] file The plugin (.so) to load - * @param[in] dlflags Arguments to dlopen(3) - * @retval plugin Plugin struct - * @retval NULL Error - */ -static struct plugin * -backend_plugin_load (clicon_handle h, - char *file, - int dlflags) -{ - void *handle; - char *name; - struct plugin *new = NULL; - - if ((handle = plugin_load(h, file, dlflags)) == NULL) - goto done; - if ((new = malloc(sizeof(*new))) == NULL) { - clicon_err(OE_UNIX, errno, "dhunk: %s", strerror(errno)); - dlclose(handle); - return NULL; - } - memset(new, 0, sizeof(*new)); - name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; - clicon_debug(2, "Loading plugin '%s'.", name); - snprintf(new->p_name, sizeof(new->p_name), "%*s", - (int)strlen(name)-2, name); - new->p_handle = handle; - if ((new->p_start = dlsym(handle, PLUGIN_START)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_START); - if ((new->p_exit = dlsym(handle, PLUGIN_EXIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_EXIT); - if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_RESET); - if ((new->p_statedata = dlsym(handle, PLUGIN_STATEDATA)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_STATEDATA); - if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN); - if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_VALIDATE); - if ((new->p_trans_complete = dlsym(handle, PLUGIN_TRANS_COMPLETE)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMPLETE); - if ((new->p_trans_commit = dlsym(handle, PLUGIN_TRANS_COMMIT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_COMMIT); - if ((new->p_trans_end = dlsym(handle, PLUGIN_TRANS_END)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_END); - if ((new->p_trans_abort = dlsym(handle, PLUGIN_TRANS_ABORT)) != NULL) - clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_ABORT); - clicon_debug(2, "Plugin '%s' loaded.\n", name); - done: - return new; -} - -/*! Request plugins to reset system state - * The system 'state' should be the same as the contents of running_db - * @param[in] h Clicon handle - * @param[in] dbname Name of database - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_reset_state(clicon_handle h, - const char *db) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_reset) { - clicon_debug(1, "Calling plugin_reset() for %s\n", - p->p_name); - if (((p->p_reset)(h, db)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_reset() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Call plugin_start in all plugins - * @param[in] h Clicon handle - * @param[in] argc Command-line arguments - * @param[in] argv Command-line arguments - * @retval 0 OK - * @retval -1 Error - */ -int -plugin_start_argv(clicon_handle h, - int argc, - char **argv) -{ - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_start) { - optind = 0; - if (((p->p_start)(h, argc, argv)) < 0) { - clicon_err(OE_FATAL, 0, "plugin_start() failed for %s\n", - p->p_name); - return -1; - } - } - } - return 0; -} - -/*! Append plugin to list - * @param[in] p Plugin - * @retval 0 OK - * @retval -1 Error - */ -static int -plugin_append(struct plugin *p) -{ - struct plugin *new; - - if ((new = realloc(_plugins, (_nplugins+1) * sizeof (*p))) == NULL) { - clicon_err(OE_UNIX, errno, "realloc"); - return -1; - } - - memset (&new[_nplugins], 0, sizeof(new[_nplugins])); - memcpy (&new[_nplugins], p, sizeof(new[_nplugins])); - _plugins = new; - _nplugins++; - - return 0; -} - -/*! Load backend plugins found in a directory - * The plugins must have the '.so' suffix - * @param[in] h Clicon handle - * @param[in] dir Backend plugin directory - * @retval 0 OK - * @retval -1 Error - */ -static int -backend_plugin_load_dir(clicon_handle h, - const char *dir) -{ - int retval = -1; - int i; - int np = 0; - int ndp; - struct stat st; - char filename[MAXPATHLEN]; - struct dirent *dp = NULL; - struct plugin *new; - struct plugin *p = NULL; - char master[MAXPATHLEN]; - char *master_plugin; - - /* Format master plugin path */ - if ((master_plugin = clicon_master_plugin(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); - goto quit; - } - snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin); - - /* Allocate plugin group object */ - /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) - goto quit; - - /* reset num plugins */ - np = 0; - - /* Master plugin must be loaded first if it exists. */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master); - if (stat(filename, &st) == 0) { - clicon_debug(1, "Loading master plugin '%.*s' ...", - (int)strlen(filename), filename); - - new = backend_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL); - if (new == NULL) - goto quit; - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* Now load the rest. Note plugins is the global variable */ - for (i = 0; i < ndp; i++) { - if (strcmp(dp[i].d_name, master) == 0) - continue; /* Skip master now */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "Loading plugin '%.*s' ...", (int)strlen(filename), filename); - new = backend_plugin_load(h, filename, RTLD_NOW); - if (new == NULL) - goto quit; - /* Append to 'plugins' */ - if (plugin_append(new) < 0) - goto quit; - free(new); - } - - /* All good. */ - retval = 0; - -quit: - if (retval != 0) { - /* XXX p is always NULL */ - if (_plugins) { - while (--np >= 0){ - if ((p = &_plugins[np]) == NULL) - continue; - backend_plugin_unload(h, p); - free(p); - } - free(_plugins); - _plugins=0; - } - } - if (dp) - free(dp); - return retval; -} - - /*! Load a plugin group. * @param[in] h Clicon handle * @retval 0 OK @@ -394,42 +85,115 @@ plugin_initiate(clicon_handle h) { char *dir; - /* First load CLICON system plugins */ - if (backend_plugin_load_dir(h, CLIXON_BACKEND_SYSDIR) < 0) - return -1; - - /* Then load application plugins */ - dir = clicon_backend_dir(h); - /* If backend directory, load the backend plugisn */ - if (dir && backend_plugin_load_dir(h, dir) < 0) - return -1; - - return 0; + /* Load application plugins */ + if ((dir = clicon_backend_dir(h)) == NULL) + return 0; + return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir); } -/*! Unload and deallocate all backend plugins +/*! Request plugins to reset system state + * The system 'state' should be the same as the contents of running_db * @param[in] h Clicon handle + * @param[in] db Name of database * @retval 0 OK * @retval -1 Error */ int -plugin_finish(clicon_handle h) +clixon_plugin_reset(clicon_handle h, + char *db) { - int i; - struct plugin *p; - - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - backend_plugin_unload(h, p); + clixon_plugin *cp = NULL; + plgreset_t *resetfn; /* Plugin auth */ + int retval = 1; + + while ((cp = plugin_each(cp)) != NULL) { + if ((resetfn = cp->cp_api.ca_reset) == NULL) + continue; + if ((retval = resetfn(h, db)) < 0) { + clicon_debug(1, "plugin_start() failed\n"); + return -1; + } + break; } - if (_plugins){ - free(_plugins); - _plugins = NULL; - } - _nplugins = 0; - return 0; + return retval; } - + +/*! Go through all backend statedata callbacks and collect state data + * This is internal system call, plugin is invoked (does not call) this function + * Backend plugins can register + * @param[in] h clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in,out] xml XML tree. + * @retval -1 Error + * @retval 0 OK + * @retval 1 Statedata callback failed + */ +int +clixon_plugin_statedata(clicon_handle h, + char *xpath, + cxobj *xtop) +{ + int retval = -1; + int i; + cxobj *x = NULL; + yang_spec *yspec; + cxobj **xvec = NULL; + size_t xlen; + clixon_plugin *cp = NULL; + plgstatedata_t *fn; /* Plugin statedata fn */ + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (xtop==NULL){ + clicon_err(OE_CFG, ENOENT, "XML tree expected"); + goto done; + } + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_statedata) == NULL) + continue; + if ((x = xml_new("config", NULL, NULL)) == NULL) + goto done; + if (fn(h, xpath, x) < 0){ + retval = 1; + goto done; /* Dont quit here on user callbacks */ + } + if (xml_merge(xtop, x, yspec) < 0) + goto done; + if (x){ + xml_free(x); + x = NULL; + } + } + /* Code complex to filter out anything that is outside of xpath */ + if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; ip_trans_begin) - if ((retval = (p->p_trans_begin)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_BEGIN); + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_begin) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_begin callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); break; - } - + } } return retval; } @@ -508,20 +271,18 @@ plugin_transaction_validate(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - struct plugin *p; - - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_validate) - if ((retval = (p->p_trans_validate)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_VALIDATE); - - break; - } + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_validate) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_validate callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + break; + } } return retval; } @@ -538,20 +299,20 @@ int plugin_transaction_complete(clicon_handle h, transaction_data_t *td) { - int i; int retval = 0; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_complete) - if ((retval = (p->p_trans_complete)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMPLETE); - - break; - } + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_complete) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_complete callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + + break; + } } return retval; } @@ -572,9 +333,9 @@ plugin_transaction_revert(clicon_handle h, { int retval = 0; transaction_data_t tr; /* revert transaction */ - int i; - struct plugin *p; - + clixon_plugin *cp = NULL; + trans_cb_t *fn; + /* Create a new reversed transaction from the original where src and target are swapped */ memcpy(&tr, td, sizeof(tr)); @@ -588,14 +349,14 @@ plugin_transaction_revert(clicon_handle h, tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; - for (i = nr-1; i>=0; i--){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((p->p_trans_commit)(h, (transaction_data)&tr) < 0){ - clicon_log(LOG_NOTICE, "Plugin '%s' %s revert callback failed", - p->p_name, PLUGIN_TRANS_COMMIT); + while ((cp = plugin_each_revert(cp, nr)) != NULL) { + if ((fn = cp->cp_api.ca_trans_commit) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed", + __FUNCTION__, cp->cp_name); break; - } + } } return retval; /* ignore errors */ } @@ -614,20 +375,22 @@ plugin_transaction_commit(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; + int i=0; - for (i = 0; i < _nplugins; i++){ - p = &_plugins[i]; - if (p->p_trans_commit) - if ((retval = (p->p_trans_commit)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_COMMIT); - /* Make an effort to revert transaction */ - plugin_transaction_revert(h, td, i); - break; - } + while ((cp = plugin_each(cp)) != NULL) { + i++; + if ((fn = cp->cp_api.ca_trans_commit) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + /* Make an effort to revert transaction */ + plugin_transaction_revert(h, td, i-1); + break; + } } return retval; } @@ -643,19 +406,18 @@ plugin_transaction_end(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_end) - if ((retval = (p->p_trans_end)(h, (transaction_data)td)) < 0){ - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_log(LOG_NOTICE, "%s: Plugin '%s' %s callback does not make clicon_err call on error", - __FUNCTION__, p->p_name, PLUGIN_TRANS_END); - - break; - } + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_end) == NULL) + continue; + if ((retval = fn(h, (transaction_data)td)) < 0){ + if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ + clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_end callback does not make clicon_err call on error", + __FUNCTION__, cp->cp_name); + break; + } } return retval; } @@ -671,94 +433,14 @@ plugin_transaction_abort(clicon_handle h, transaction_data_t *td) { int retval = 0; - int i; - struct plugin *p; + clixon_plugin *cp = NULL; + trans_cb_t *fn; - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_trans_abort) - (p->p_trans_abort)(h, (transaction_data)td); /* dont abort on error */ + while ((cp = plugin_each(cp)) != NULL) { + if ((fn = cp->cp_api.ca_trans_abort) == NULL) + continue; + fn(h, (transaction_data)td); /* dont abort on error */ } return retval; } -/*---------------------------------------------------------------------- - * Backend state data callbacks - */ - -/*! Go through all backend statedata callbacks and collect state data - * This is internal system call, plugin is invoked (does not call) this function - * Backend plugins can register - * @param[in] h clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in,out] xml XML tree. - * @retval -1 Error - * @retval 0 OK - * @retval 1 Statedata callback failed - */ -int -backend_statedata_call(clicon_handle h, - char *xpath, - cxobj *xtop) -{ - int retval = -1; - struct plugin *p; - int i; - cxobj *x = NULL; - yang_spec *yspec; - cxobj **xvec = NULL; - size_t xlen; - - if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if (xtop==NULL){ - clicon_err(OE_CFG, ENOENT, "XML tree expected"); - goto done; - } - for (i = 0; i < _nplugins; i++) { - p = &_plugins[i]; - if (p->p_statedata) { - if ((x = xml_new("config", NULL, NULL)) == NULL) - goto done; - if ((p->p_statedata)(h, xpath, x) < 0){ - retval = 1; - goto done; /* Dont quit here on user callbacks */ - } - if (xml_merge(xtop, x, yspec) < 0) - goto done; - if (x){ - xml_free(x); - x = NULL; - } - } - } - /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) - goto done; - - /* If vectors are specified then mark the nodes found and - * then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i +#include +#include +#include +#include +#include +#include + +/* clicon */ +#include + +/* Clicon library functions. */ +#include + +/* These include signatures for plugin and transaction callbacks. */ +#include + + +int +transaction_commit_2(clicon_handle h, + transaction_data td) +{ + clicon_debug(1, "%s", __FUNCTION__); + return 0; +} + +int +plugin_start_2(clicon_handle h, + int argc, + char **argv) +{ + return 0; +} + +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +static clixon_plugin_api api = { + "secondary", /* name */ + clixon_plugin_init, /* init */ + plugin_start_2, /* start */ + NULL, /* exit */ + NULL, /* auth */ + NULL, /* reset */ + NULL, /* statedata */ + NULL, /* trans begin */ + NULL, /* trans validate */ + NULL, /* trans complete */ + transaction_commit_2,/* trans commit */ + NULL, /* trans end */ + NULL /* trans abort */ +}; + +/*! Backend plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + clicon_debug(1, "%s backend secondary", __FUNCTION__); + return &api; +} diff --git a/example/example_netconf.c b/example/example_netconf.c index 65883e2c..f574de6f 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -64,19 +64,23 @@ plugin_exit(clicon_handle h) clixon_plugin_api * clixon_plugin_init(clicon_handle h); -static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - plugin_start, - plugin_exit, - NULL +static struct clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + plugin_start, /* start */ + plugin_exit, /* exit */ + NULL /* auth */ }; /*! Netconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct */ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { - return (void*)&api; + clicon_debug(1, "%s restconf", __FUNCTION__); + return &api; } diff --git a/example/example_restconf.c b/example/example_restconf.c index d3340a2f..87c378ed 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -269,19 +269,22 @@ plugin_credentials(clicon_handle h, clixon_plugin_api * clixon_plugin_init(clicon_handle h); -static const struct clixon_plugin_api api = { - "example", - clixon_plugin_init, - NULL, - NULL, - plugin_credentials, +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + plugin_credentials /* auth */ }; /*! Restconf plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct */ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); - return (void*)&api; + return &api; } diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 3c9fb28f..82a44adc 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -73,7 +73,6 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ /*! Called by restconf to check credentials and return username */ -#define PLUGIN_CREDENTIALS "plugin_credentials" /* Plugin authorization. Set username option (or not) * @param[in] Clicon handle @@ -98,21 +97,12 @@ struct clixon_plugin_api; typedef struct clixon_plugin_api* (plginit2_t)(clicon_handle); /* Clixon plugin Init */ struct clixon_plugin_api{ - char ca_name[PATH_MAX]; /* Name of plugin */ + char ca_name[PATH_MAX]; /* Name of plugin (given by plugin) */ plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ plgstart_t *ca_start; /* Plugin start */ plgexit_t *ca_exit; /* Plugin exit */ plgauth_t *ca_auth; /* Auth credentials */ -}; - -struct clixon_backend_api{ - char ca_name[PATH_MAX]; /* Name of plugin */ - plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ - plgstart_t *ca_start; /* Plugin start */ - plgexit_t *ca_exit; /* Plugin exit */ - plgauth_t *ca_auth; /* Auth credentials */ - /*--Above here common fields with clixon_netconf_api, clixon_restconf_api, - * etc ----------*/ + /*--Above here common fields w clixon_backend_api ----------*/ plgreset_t *ca_reset; /* Reset system status (backend only) */ plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */ @@ -125,7 +115,6 @@ struct clixon_backend_api{ }; typedef struct clixon_plugin_api clixon_plugin_api; -#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ /*! Called when plugin loaded. Only mandadory callback. All others optional * @see plginit_t */ @@ -133,24 +122,43 @@ typedef struct clixon_plugin_api clixon_plugin_api; /* Internal plugin structure with dlopen() handle and plugin_api */ struct clixon_plugin{ + char cp_name[PATH_MAX]; /* Plugin filename. Note api ca_name is given by plugin itself */ plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */ struct clixon_plugin_api cp_api; }; typedef struct clixon_plugin clixon_plugin; +/* + * Pseudo-Prototypes + * User-defineed plugins, not in library code + */ +#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ + +/*! Plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + /* * Prototypes */ -int clixon_plugins_load(clicon_handle h, char *dir); +clixon_plugin *plugin_each(clixon_plugin *cpprev); +clixon_plugin *plugin_each_revert(clixon_plugin *cpprev, int nr); +int clixon_plugins_load(clicon_handle h, char *function, char *dir); + +/* obsolete */ plghndl_t plugin_load (clicon_handle h, char *file, int dlflags); +/* obsolete */ int plugin_unload(clicon_handle h, plghndl_t *handle); -int clixon_plugin_unload(clicon_handle h); - int clixon_plugin_start(clicon_handle h, int argc, char **argv); +int clixon_plugin_exit(clicon_handle h); + int clixon_plugin_auth(clicon_handle h, void *arg); #endif /* _CLIXON_PLUGIN_H_ */ diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index a1a552bd..2377352d 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -62,8 +62,7 @@ #include "clixon_string.h" #include "clixon_file.h" -/* - * qsort function +/*! qsort "compar" for directory alphabetically sorting, see qsort(3) */ static int clicon_file_dirent_sort(const void* arg1, @@ -79,8 +78,7 @@ clicon_file_dirent_sort(const void* arg1, #endif /* HAVE_STRVERSCMP */ } - -/*! Return sorted matching files from a directory +/*! Return alphabetically sorted files from a directory matching regexp * @param[in] dir Directory path * @param[out] ent Entries pointer, will be filled in with dir entries. Free * after use @@ -120,16 +118,13 @@ clicon_file_dirent(const char *dir, struct dirent *new = NULL; struct dirent *dvecp = NULL; - *ent = NULL; nent = 0; - if (regexp && (res = regcomp(&re, regexp, REG_EXTENDED)) != 0) { regerror(res, &re, errbuf, sizeof(errbuf)); clicon_err(OE_DB, 0, "regcomp: %s", errbuf); return -1; } - if ((dirp = opendir (dir)) == NULL) { if (errno == ENOENT) /* Dir does not exist -> return 0 matches */ retval = 0; @@ -137,7 +132,6 @@ clicon_file_dirent(const char *dir, clicon_err(OE_UNIX, errno, "opendir(%s)", dir); goto quit; } - for (res = readdir_r (dirp, &dent, &dresp); dresp; res = readdir_r (dirp, &dent, &dresp)) { @@ -162,7 +156,6 @@ clicon_file_dirent(const char *dir, if ((type & st.st_mode) == 0) continue; } - if ((tmp = realloc(new, (nent+1)*sizeof(*dvecp))) == NULL) { clicon_err(OE_UNIX, errno, "realloc"); goto quit; diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 6a5f589e..8b789ac1 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -64,9 +64,83 @@ static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */ static int _clixon_nplugins = 0; /* Number of plugins */ +/*! Iterator over clixon plugins + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = plugin_each(cp)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list + */ +clixon_plugin * +plugin_each(clixon_plugin *cpprev) +{ + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; + + if (cpprev == NULL) + cpnext = _clixon_plugins; + else{ + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; + } + if (cp && i < _clixon_nplugins-1) + cpnext = &_clixon_plugins[i+1]; + } + return cpnext; +} + +/*! Reverse iterator over clixon plugins, iterater from nr to 0 + * + * @note Never manipulate the plugin during operation or using the + * same object recursively + * + * @param[in] plugin previous plugin, or NULL on init + * @code + * clicon_plugin *cp = NULL; + * while ((cp = plugin_each_revert(cp, nr)) != NULL) { + * ... + * } + * @endcode + * @note Not optimized, alwasy iterates from the start of the list + */ +clixon_plugin * +plugin_each_revert(clixon_plugin *cpprev, + int nr) +{ + int i; + clixon_plugin *cp; + clixon_plugin *cpnext = NULL; + + if (cpprev == NULL) + cpnext = &_clixon_plugins[nr-1]; + else{ + for (i = nr-1; i >= 0; i--) { + cp = &_clixon_plugins[i]; + if (cp == cpprev) + break; + cp = NULL; + } + if (cp && i > 0) + cpnext = &_clixon_plugins[i-1]; + } + return cpnext; +} + /*! Load a dynamic plugin object and call its init-function * @param[in] h Clicon handle * @param[in] file Which plugin to load + * @param[in] function Which function symbol to load and call * @param[in] dlflags See man(3) dlopen * @retval cp Clixon plugin structure * @retval NULL Error @@ -74,7 +148,8 @@ static int _clixon_nplugins = 0; /* Number of plugins */ */ static clixon_plugin * plugin_load_one(clicon_handle h, - char *file, + char *file, + char *function, int dlflags) { char *error; @@ -82,16 +157,17 @@ plugin_load_one(clicon_handle h, plginit2_t *initfn; clixon_plugin_api *api = NULL; clixon_plugin *cp = NULL; + char *name; clicon_debug(1, "%s", __FUNCTION__); dlerror(); /* Clear any existing error */ - if ((handle = dlopen (file, dlflags)) == NULL) { + if ((handle = dlopen(file, dlflags)) == NULL) { error = (char*)dlerror(); clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); goto done; } - /* call plugin_init() if defined */ - if ((initfn = dlsym(handle, CLIXON_PLUGIN_INIT)) == NULL){ + /* call plugin_init() if defined, eg CLIXON_PLUGIN_INIT or CLIXON_BACKEND_INIT */ + if ((initfn = dlsym(handle, function)) == NULL){ clicon_err(OE_PLUGIN, errno, "Failed to find %s when loading clixon plugin %s", CLIXON_PLUGIN_INIT, file); goto err; } @@ -106,11 +182,15 @@ plugin_load_one(clicon_handle h, file); goto err; } - if ((cp = (clixon_plugin *)malloc(sizeof(*cp))) == NULL){ + /* Note: sizeof clixon_plugin_api which is largest of clixon_plugin_api:s */ + if ((cp = (clixon_plugin *)malloc(sizeof(struct clixon_plugin))) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); goto done; } cp->cp_handle = handle; + name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; + snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", + (int)strlen(name)-2, name); cp->cp_api = *api; clicon_debug(1, "%s", __FUNCTION__); done: @@ -123,12 +203,14 @@ plugin_load_one(clicon_handle h, /*! Load a set of plugin objects from a directory and and call their init-function * @param[in] h Clicon handle + * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT) * @param[in] dir Directory. .so files in this dir will be loaded. * @retval 0 OK * @retval -1 Error */ int clixon_plugins_load(clicon_handle h, + char *function, char *dir) { int retval = -1; @@ -147,7 +229,7 @@ clixon_plugins_load(clicon_handle h, snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); clicon_debug(1, "DEBUG: Loading plugin '%.*s' ...", (int)strlen(filename), filename); - if ((cp = plugin_load_one(h, filename, RTLD_NOW)) == NULL) + if ((cp = plugin_load_one(h, filename, function, RTLD_NOW)) == NULL) goto done; _clixon_nplugins++; if ((_clixon_plugins = realloc(_clixon_plugins, _clixon_nplugins*sizeof(clixon_plugin))) == NULL) { @@ -169,7 +251,7 @@ done: * @param[in] h Clicon handle * @param[in] file Which plugin to load * @param[in] dlflags See man(3) dlopen - * @see plugin_load_one for netxgen, this is soon OBSOLETE + * @note OBSOLETE */ plghndl_t plugin_load(clicon_handle h, @@ -211,10 +293,10 @@ plugin_load(clicon_handle h, return NULL; } - /*! Unload a plugin * @param[in] h Clicon handle * @param[in] handle Clicon handle + * @note OBSOLETE */ int plugin_unload(clicon_handle h, @@ -238,27 +320,6 @@ plugin_unload(clicon_handle h, return retval; } -/*! Unload all plugins - * @param[in] h Clicon handle - */ -int -clixon_plugin_unload(clicon_handle h) -{ - clixon_plugin *cp; - int i; - - for (i = 0; i < _clixon_nplugins; i++) { - cp = &_clixon_plugins[i]; - plugin_unload(h, cp->cp_handle); - } - if (_clixon_plugins){ - free(_clixon_plugins); - _clixon_plugins = NULL; - } - _clixon_nplugins = 0; - return 0; -} - /*! Call plugin_start in all plugins * @param[in] h Clicon handle */ @@ -275,6 +336,7 @@ clixon_plugin_start(clicon_handle h, cp = &_clixon_plugins[i]; if ((startfn = cp->cp_api.ca_start) == NULL) continue; + // optind = 0; if (startfn(h, argc, argv) < 0) { clicon_debug(1, "plugin_start() failed\n"); return -1; @@ -283,6 +345,39 @@ clixon_plugin_start(clicon_handle h, return 0; } +/*! Unload all plugins: call exit function and close shared handle + * @param[in] h Clicon handle + */ +int +clixon_plugin_exit(clicon_handle h) +{ + clixon_plugin *cp; + plgexit_t *exitfn; + int i; + char *error; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if ((exitfn = cp->cp_api.ca_exit) == NULL) + continue; + if (exitfn(h) < 0) { + clicon_debug(1, "plugin_exit() failed\n"); + return -1; + } + if (dlclose(cp->cp_handle) != 0) { + error = (char*)dlerror(); + clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); + } + } + if (_clixon_plugins){ + free(_clixon_plugins); + _clixon_plugins = NULL; + } + _clixon_nplugins = 0; + return 0; +} + + /*! Run the restconf user-defined credentials callback if present * Find first authentication callback and call that, then return. * The callback is to set the authenticated user diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index eaaf33c4..6811e550 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -433,7 +433,7 @@ xml_child_i_set(cxobj *xt, /*! Iterator over xml children objects * - * NOTE: Never manipulate the child-list during operation or using the + * @note Never manipulate the child-list during operation or using the * same object recursively, the function uses an internal field to remember the * index used. It works as long as the same object is not iterated concurrently. * From 843b6abbcbced403a0c393cc389ca852ffcfbbc9 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Wed, 4 Apr 2018 09:51:47 +0000 Subject: [PATCH 28/50] plugin API remake --- apps/backend/clixon_backend.h | 58 ----------------------------------- apps/restconf/restconf_main.c | 6 ++-- example/example_restconf.c | 3 +- 3 files changed, 4 insertions(+), 63 deletions(-) diff --git a/apps/backend/clixon_backend.h b/apps/backend/clixon_backend.h index 7acbdb5b..b3a75763 100644 --- a/apps/backend/clixon_backend.h +++ b/apps/backend/clixon_backend.h @@ -49,62 +49,4 @@ #include #include -/*! Clicon Backend plugin callbacks: use these in your backend plugin code - */ - -/*! Called when plugin loaded. Only mandadory callback. All others optional - * @see plginit_t - */ -int plugin_init(clicon_handle h); - -/* Called when backend started with cmd-line arguments from daemon call. - * @see plgstart_t - */ -int plugin_start(clicon_handle h, int argc, char **argv); - -/* Called just before plugin unloaded. - * @see plgexit_t - */ -int plugin_exit(clicon_handle h); - -/*! Reset system state to original state. Eg at reboot before running thru config. - * @see plgreset_t - */ -int plugin_reset(clicon_handle h, const char *db); - -/*! Retreive statedata, add statedata to XML tree - * @see plgstatedata_ t - */ -int plugin_statedata(clicon_handle h, char *xpath, cxobj *xtop); - -/*! Called before a commit/validate sequence begins. Eg setup state before commit - * @see trans_cb_t - */ -int transaction_begin(clicon_handle h, transaction_data td); - -/*! Validate. - * @see trans_cb_t - */ -int transaction_validate(clicon_handle h, transaction_data td); - -/* Called after a validation completed succesfully (but before commit). - * @see trans_cb_t - */ -int transaction_complete(clicon_handle h, transaction_data td); - -/* Commit. - * @see trans_cb_t - */ -int transaction_commit(clicon_handle h, transaction_data td); - -/* Called after a commit sequence completed succesfully. - * @see trans_cb_t - */ -int transaction_end(clicon_handle h, transaction_data td); - -/* Called if commit or validate sequence fails. After eventual rollback. - * @see trans_cb_t - */ -int transaction_abort(clicon_handle h, transaction_data td); - #endif /* _CLIXON_BACKEND_H_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 86d7f495..12ff965f 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -321,7 +321,7 @@ api_yang_library_version(clicon_handle h, */ static int api_restconf(clicon_handle h, - FCGX_Request *r) + FCGX_Request *r) { int retval = -1; char *path; @@ -374,14 +374,15 @@ api_restconf(clicon_handle h, goto done; data = cbuf_get(cb); clicon_debug(1, "DATA=%s", data); + if (str2cvec(data, '&', '=', &dvec) < 0) goto done; - /* If present, check credentials. See "plugin_credentials" in plugin * See RFC 8040 section 2.5 */ if ((authenticated = clixon_plugin_auth(h, r)) < 0) goto done; + /* If set but no user, we set a dummy user */ if (authenticated){ if (clicon_username_get(h) == NULL) @@ -424,7 +425,6 @@ api_restconf(clicon_handle h, return retval; } - static int restconf_terminate(clicon_handle h) { diff --git a/example/example_restconf.c b/example/example_restconf.c index 87c378ed..380326ec 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -184,8 +184,7 @@ b64_decode(const char *src, /*! Process a rest request that requires (cookie) "authentication" * Note, this is loaded as dlsym fixed symbol in plugin * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle - * @param[out] username Malloced username, or NULL. + * @param[in] arg Argument. Here: Fastcgi request handle * @retval -1 Fatal error * @retval 0 Unauth * @retval 1 Auth From 7a4371e76f8aa84afa090529a86c494a2d955378 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sat, 7 Apr 2018 08:09:55 +0000 Subject: [PATCH 29/50] debug function --- apps/restconf/restconf_main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 12ff965f..d7912446 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -364,7 +364,7 @@ api_restconf(clicon_handle h, retval = notfound(r); goto done; } - clicon_debug(1, "method=%s", method); + clicon_debug(1, "%s: method=%s", __FUNCTION__, method); if (str2cvec(query, '&', '=', &qvec) < 0) goto done; if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */ @@ -373,7 +373,7 @@ api_restconf(clicon_handle h, if ((cb = readdata(r)) == NULL) goto done; data = cbuf_get(cb); - clicon_debug(1, "DATA=%s", data); + clicon_debug(1, "%s DATA=%s", __FUNCTION__, data); if (str2cvec(data, '&', '=', &dvec) < 0) goto done; @@ -382,6 +382,7 @@ api_restconf(clicon_handle h, */ if ((authenticated = clixon_plugin_auth(h, r)) < 0) goto done; + clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); /* If set but no user, we set a dummy user */ if (authenticated){ @@ -392,6 +393,7 @@ api_restconf(clicon_handle h, unauthorized(r); goto ok; } + clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); if (strcmp(method, "yang-library-version")==0){ if (api_yang_library_version(h, r) < 0) goto done; From d541c49c6f23d5a7b8808d62a33d40b689f28bcd Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 7 Apr 2018 18:20:33 +0200 Subject: [PATCH 30/50] Plugin RPC callback interface have been unified between backend, netconf and restconf. * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. --- CHANGELOG.md | 7 +- apps/backend/backend_client.c | 4 +- apps/backend/backend_main.c | 2 +- apps/backend/clixon_backend_handle.c | 107 -------------------- apps/backend/clixon_backend_handle.h | 40 ++------ apps/backend/clixon_backend_transaction.h | 10 +- apps/netconf/clixon_netconf.h | 15 --- apps/netconf/netconf_lib.c | 50 +++++----- apps/netconf/netconf_main.c | 3 +- apps/netconf/netconf_rpc.c | 32 ++++-- apps/restconf/restconf_main.c | 4 +- apps/restconf/restconf_methods.c | 26 ++++- example/example.yang | 16 +++ example/example_backend.c | 40 ++++---- example/example_netconf.c | 16 +++ example/example_restconf.c | 18 ++++ lib/clixon/clixon_plugin.h | 15 +++ lib/src/clixon_plugin.c | 115 ++++++++++++++++++++++ test/test_netconf.sh | 4 +- test/test_restconf.sh | 28 +++++- 20 files changed, 323 insertions(+), 229 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9ab88eb..fd1171f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,18 @@ ## 3.6.0 (Upcoming) - ### Major changes: * Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. + * Plugin RPC callback interface have been unified between backend, netconf and restconf. + * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` + * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. * Moved specific plugin functions from apps/ to generic functions in lib/ * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() * Removed some obscure plugin code that seem not to be used (please report if needed!) - * Client-local netconf plugins netconf_plugin_callbacks() * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() @@ -20,6 +22,7 @@ * Example of migrating a backend plugin module: * Add all callbacks in a clixon_plugin_api struct * Rename plugin_init() -> clixon_plugin_init() and return api as function value + * Rename backend_rpc_cb_register() -> rpc_callback_register() for any RPC/restconf operation POST calls ``` /* This is old style with hardcoded function names (eg plugin_start) */ int plugin_start(clicon_handle h, int argc, char **argv) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index cf0bd2a5..97b583da 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -909,10 +909,10 @@ from_client_msg(clicon_handle h, } else{ clicon_err_reset(); - if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0){ + if ((ret = rpc_callback_call(h, xe, cbret, ce)) < 0){ if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; - clicon_log(LOG_NOTICE, "%s Error in backend_rpc_call:%s", __FUNCTION__, xml_name(xe)); + clicon_log(LOG_NOTICE, "%s Error in rpc_callback_call:%s", __FUNCTION__, xml_name(xe)); goto reply; /* Dont quit here on user callbacks */ } if (ret == 0){ /* not handled by callback */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 45c097c9..ed2e9e64 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -88,7 +88,7 @@ backend_terminate(clicon_handle h) yspec_free(yspec); clixon_plugin_exit(h); /* Delete all backend plugin RPC callbacks */ - backend_rpc_cb_delete_all(); + rpc_callback_delete_all(); if (pidfile) unlink(pidfile); if (sockpath) diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index aeba8a23..de6563dc 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -430,110 +430,3 @@ subscription_each(clicon_handle h, return hs; } -/*-------------------------------------------------------------------- - * Backend netconf rpc callbacks - */ -typedef struct { - qelem_t rc_qelem; /* List header */ - backend_rpc_cb rc_callback; /* RPC Callback */ - void *rc_arg; /* Application specific argument to cb */ - char *rc_tag; /* Xml tag when matched, callback called */ -} backend_rpc_cb_entry; - -/* List of backend rpc callback entries */ -static backend_rpc_cb_entry *rpc_cb_list = NULL; - -/*! Register netconf backend rpc callback - * Called from plugin to register a callback for a specific netconf XML tag. - * - * @param[in] h clicon handle - * @param[in] cb, Callback called - * @param[in] arg, Arg to send to callback - * @param[in] tag Xml tag when callback is made - * @see backend_rpc_cb_call - */ -int -backend_rpc_cb_register(clicon_handle h, - backend_rpc_cb cb, - void *arg, - char *tag) -{ - backend_rpc_cb_entry *rc; - - if ((rc = malloc(sizeof(backend_rpc_cb_entry))) == NULL) { - clicon_err(OE_DB, errno, "malloc: %s", strerror(errno)); - goto catch; - } - memset (rc, 0, sizeof (*rc)); - rc->rc_callback = cb; - rc->rc_arg = arg; - rc->rc_tag = strdup(tag); /* XXX strdup memleak */ - INSQ(rc, rpc_cb_list); - return 0; -catch: - if (rc){ - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } - return -1; -} - -/*! Search netconf backend callbacks and invoke if match - * This is internal system call, plugin is invoked (does not call) this functino - * @param[in] h clicon handle - * @param[in] xe Sub-tree (under xorig) at child of rpc: . - * @param[in] ce Client (session) entry - * @param[out] cbret Return XML, error or OK as cbuf - * - * @retval -1 Error - * @retval 0 OK, not found handler. - * @retval 1 OK, handler called - * @see backend_rpc_cb_register - */ -int -backend_rpc_cb_call(clicon_handle h, - cxobj *xe, - struct client_entry *ce, - cbuf *cbret) -{ - backend_rpc_cb_entry *rc; - int retval = -1; - - if (rpc_cb_list == NULL) - return 0; - rc = rpc_cb_list; - do { - if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ - if ((retval = rc->rc_callback(h, xe, ce, cbret, rc->rc_arg)) < 0){ - clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); - goto done; - } - else{ - retval = 1; /* handled */ - goto done; - } - } - rc = NEXTQ(backend_rpc_cb_entry *, rc); - } while (rc != rpc_cb_list); - retval = 0; - done: - return retval; -} - -/*! Delete all state data callbacks. - */ -int -backend_rpc_cb_delete_all(void) -{ - backend_rpc_cb_entry *rc; - - while((rc = rpc_cb_list) != NULL) { - DELQ(rc, rpc_cb_list, backend_rpc_cb_entry *); - if (rc->rc_tag) - free(rc->rc_tag); - free(rc); - } - return 0; -} - diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index fa596ed0..3855a14b 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -43,30 +43,6 @@ /* * Types */ -struct client_entry; -typedef int (*backend_rpc_cb)( - clicon_handle h, /* CLicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret,/* Reply eg ... */ - void *arg /* Argument given at register */ -); -typedef backend_rpc_cb backend_netconf_cb_t; /* XXX backward compat */ - - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - -/* - * Log for netconf notify function (config_client.c) - */ -int backend_notify(clicon_handle h, char *stream, int level, char *txt); -int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); - /* subscription callback */ typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg); @@ -82,6 +58,14 @@ struct handle_subscription{ void *hs_arg; /* Callback argument */ }; +/* + * Prototypes + */ +/* Log for netconf notify function (config_client.c) */ +int backend_notify(clicon_handle h, char *stream, int level, char *txt); +int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x); + + struct handle_subscription *subscription_add(clicon_handle h, char *stream, enum format_enum format, char *filter, subscription_fn_t fn, void *arg); @@ -92,12 +76,4 @@ int subscription_delete(clicon_handle h, char *stream, struct handle_subscription *subscription_each(clicon_handle h, struct handle_subscription *hprev); -int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg, - char *tag); - -int backend_rpc_cb_call(clicon_handle h, cxobj *xe, struct client_entry *ce, - cbuf *cbret); - -int backend_rpc_cb_delete_all(void); - #endif /* _CLIXON_BACKEND_HANDLE_H_ */ diff --git a/apps/backend/clixon_backend_transaction.h b/apps/backend/clixon_backend_transaction.h index 1d2a523e..a290c5f1 100644 --- a/apps/backend/clixon_backend_transaction.h +++ b/apps/backend/clixon_backend_transaction.h @@ -41,16 +41,8 @@ #define _CLIXON_BACKEND_TRANSACTION_H_ /* - * Types + * Prototypes */ - -/*! Generic downcall registration. - * Enables any function to be called from (cli) frontend - * to backend. Like an RPC on application-level. - */ -typedef int (*downcall_cb)(clicon_handle h, uint16_t op, uint16_t len, - void *arg, uint16_t *retlen, void **retarg); - /* Transaction callback data accessors for client plugins * (defined in config_dbdep.c) * @see transaction_data_t internal structure diff --git a/apps/netconf/clixon_netconf.h b/apps/netconf/clixon_netconf.h index e3b1680e..e2fbb5bb 100644 --- a/apps/netconf/clixon_netconf.h +++ b/apps/netconf/clixon_netconf.h @@ -39,27 +39,12 @@ #ifndef _CLIXON_NETCONF_H_ #define _CLIXON_NETCONF_H_ -/* - * Types - */ -typedef int (*netconf_cb_t)( - clicon_handle h, - cxobj *xn, /* Request: */ - cxobj **xret, /* Return xml tree, eg ... */ - void *arg /* Argument given at netconf_register_callback() */ - ); - /* * Prototypes * (Duplicated. Also in netconf_*.h) */ int netconf_output(int s, cbuf *xf, char *msg); -int netconf_register_callback(clicon_handle h, - netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag); /* Xml tag when callback is made */ - int netconf_xpath(cxobj *xsearch, cxobj *xfilter, cbuf *xf, cbuf *xf_err, diff --git a/apps/netconf/netconf_lib.c b/apps/netconf/netconf_lib.c index debd0c3d..5e5e21d2 100644 --- a/apps/netconf/netconf_lib.c +++ b/apps/netconf/netconf_lib.c @@ -71,45 +71,48 @@ enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */ int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */ +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. + * @param[in] cb Netconf packet (cligen buffer) + */ int -add_preamble(cbuf *xf) +add_preamble(cbuf *cb) { if (transport == NETCONF_SOAP) - cprintf(xf, "\n\n" + cprintf(cb, "\n\n" ""); return 0; } -/* - * add_postamble - * add netconf xml postamble of message. That is, xml after the body of the message. +/*! Add netconf xml postamble of message. I.e, xml after the body of the message. * for soap this is the envelope stuff, for ssh this is ]]>]]> + * @param[in] cb Netconf packet (cligen buffer) */ int -add_postamble(cbuf *xf) +add_postamble(cbuf *cb) { switch (transport){ case NETCONF_SSH: - cprintf(xf, "]]>]]>"); /* Add RFC4742 end-of-message marker */ + cprintf(cb, "]]>]]>"); /* Add RFC4742 end-of-message marker */ break; case NETCONF_SOAP: - cprintf(xf, "\n" ""); + cprintf(cb, "\n" ""); break; } return 0; } -/* - * add_error_preamble +/*! Add error_preamble * compared to regular messages (see add_preamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_preamble(cbuf *xf, char *reason) +add_error_preamble(cbuf *cb, + char *reason) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" + cprintf(cb, "" "" "" "" @@ -121,26 +124,26 @@ add_error_preamble(cbuf *xf, char *reason) "", reason); break; default: - if (add_preamble(xf) < 0) + if (add_preamble(cb) < 0) return -1; break; } return 0; } -/* - * add_error_postamble +/*! Add error postamble * compared to regular messages (see add_postamble), error message differ in some * protocols (eg soap) by adding a longer and deeper header. + * @param[in] cb Netconf packet (cligen buffer) */ int -add_error_postamble(cbuf *xf) +add_error_postamble(cbuf *cb) { switch (transport){ case NETCONF_SOAP: - cprintf(xf, "" ""); + cprintf(cb, "" ""); default: /* fall through */ - if (add_postamble(xf) < 0) + if (add_postamble(cb) < 0) return -1; break; } @@ -150,7 +153,9 @@ add_error_postamble(cbuf *xf) /*! Get "target" attribute, return actual database given candidate or running * Caller must do error handling - * @retval dbname Actual database file name + * @param[in] xn XML tree + * @param[in] path + * @retval dbname Actual database file name */ char * netconf_get_target(cxobj *xn, @@ -180,11 +185,11 @@ netconf_get_target(cxobj *xn, */ int netconf_output(int s, - cbuf *xf, + cbuf *cb, char *msg) { - char *buf = cbuf_get(xf); - int len = cbuf_len(xf); + char *buf = cbuf_get(cb); + int len = cbuf_len(cb); int retval = -1; clicon_debug(1, "SEND %s", msg); @@ -207,3 +212,4 @@ netconf_output(int s, done: return retval; } + diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index f219792e..ad900535 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -262,6 +262,8 @@ netconf_terminate(clicon_handle h) { yang_spec *yspec; + clixon_plugin_exit(h); + rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -402,7 +404,6 @@ main(int argc, if (event_loop() < 0) goto done; done: - clixon_plugin_exit(h); netconf_terminate(h); clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid()); diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 8cc23e1b..5c8bc143 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -849,8 +849,10 @@ netconf_create_subscription(clicon_handle h, return retval; } -/*! See if there is any callback registered for this tag +/*! See if there is any application defined RPC for this tag * + * This may either be local client-side or backend. If backend send as netconf + * RPC. * @param[in] h clicon handle * @param[in] xn Sub-tree (under xorig) at child of rpc: . * @param[out] xret Return XML, error or OK @@ -871,6 +873,8 @@ netconf_application_rpc(clicon_handle h, yang_stmt *youtput; cxobj *xoutput; cbuf *cb = NULL; + cbuf *cbret = NULL; + int ret; /* First check system / netconf RPC:s */ if ((cb = cbuf_new()) == NULL){ @@ -891,9 +895,9 @@ netconf_application_rpc(clicon_handle h, /* Find yang rpc statement, return yang rpc statement if found */ if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) goto done; - /* Check if found */ if (yrpc != NULL){ + /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) @@ -904,13 +908,20 @@ netconf_application_rpc(clicon_handle h, if (xml_yang_validate_add(xn, NULL) < 0) goto done; } - /* - * 1. Check xn arguments with input statement. - * 2. Send to backend as clicon_msg-encode() - * 3. In backend to similar but there call actual backend - */ - if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; + } + /* Look for local (client-side) netconf plugins. */ + if ((ret = rpc_callback_call(h, xn, cbret, NULL)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + goto done; + } + else /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; /* Sanity check of outgoing XML */ if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); @@ -930,9 +941,10 @@ netconf_application_rpc(clicon_handle h, done: if (cb) cbuf_free(cb); + if (cbret) + cbuf_free(cbret); return retval; } - /*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions. @@ -1017,6 +1029,8 @@ netconf_rpc_dispatch(clicon_handle h, } /* Others */ else { + /* Look for application-defined RPC. This may either be local + client-side or backend. If backend send as netconf RPC. */ if ((retval = netconf_application_rpc(h, xe, xret)) < 0) goto done; if (retval == 0){ /* not handled by callback */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index d7912446..89d52114 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -432,7 +432,8 @@ restconf_terminate(clicon_handle h) { yang_spec *yspec; - clicon_debug(0, "%s", __FUNCTION__); + clixon_plugin_exit(h); + rpc_callback_delete_all(); clicon_rpc_close_session(h); if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); @@ -605,7 +606,6 @@ main(int argc, } retval = 0; done: - clixon_plugin_exit(h); restconf_terminate(h); return retval; } diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 547aa3ec..e28bcb89 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1064,6 +1064,7 @@ api_operations_post(clicon_handle h, cxobj *xret = NULL; cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ + cxobj *xe; cxobj *xbot = NULL; yang_node *y = NULL; cxobj *xinput; @@ -1071,6 +1072,8 @@ api_operations_post(clicon_handle h, cxobj *x; cxobj *xa; char *username; + cbuf *cbret = NULL; + int ret = 0; clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); if ((yspec = clicon_dbspec_yang(h)) == NULL){ @@ -1109,7 +1112,7 @@ api_operations_post(clicon_handle h, /* XXX: something strange for rpc user */ if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; -#if 1 +#if 0 { cbuf *c = cbuf_new(); clicon_xml2cbuf(c, xtop, 0, 0); @@ -1150,9 +1153,24 @@ api_operations_post(clicon_handle h, } } } - /* Send to backend */ - if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, 0, "cbuf_new"); goto done; + } + xe = NULL; + while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { + /* Look for local (client-side) restconf plugins. */ + if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) + goto done; + if (ret == 1){ /* Handled locally */ + if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) + goto done; + } + break; /* Just one if local */ + } + if (ret == 0) /* Send to backend */ + if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) + goto done; if ((cbx = cbuf_new()) == NULL) goto done; xoutput=xpath_first(xret, "/"); @@ -1198,5 +1216,7 @@ api_operations_post(clicon_handle h, xml_free(xret); if (cbx) cbuf_free(cbx); + if (cbret) + cbuf_free(cbret); return retval; } diff --git a/example/example.yang b/example/example.yang index eb4dbfdd..42d8236f 100644 --- a/example/example.yang +++ b/example/example.yang @@ -25,4 +25,20 @@ module example { type string; } } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } diff --git a/example/example_backend.c b/example/example_backend.c index 1c4bba28..da16ab0b 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -125,9 +125,9 @@ notification_timer_setup(clicon_handle h) static int fib_route(clicon_handle h, /* Clicon handle */ cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, "" "ipv4" @@ -139,10 +139,10 @@ fib_route(clicon_handle h, /* Clicon handle */ /*! Smallest possible RPC declaration for test */ static int empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; @@ -152,9 +152,9 @@ empty(clicon_handle h, /* Clicon handle */ static int route_count(clicon_handle h, cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; @@ -278,20 +278,20 @@ clixon_plugin_init(clicon_handle h) if (notification_timer_setup(h) < 0) goto done; /* Register callback for routing rpc calls */ - if (backend_rpc_cb_register(h, fib_route, - NULL, - "fib-route"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, fib_route, + NULL, + "fib-route"/* Xml tag when callback is made */ + ) < 0) goto done; - if (backend_rpc_cb_register(h, route_count, - NULL, - "route-count"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, route_count, + NULL, + "route-count"/* Xml tag when callback is made */ + ) < 0) goto done; - if (backend_rpc_cb_register(h, empty, - NULL, - "empty"/* Xml tag when callback is made */ - ) < 0) + if (rpc_callback_register(h, empty, + NULL, + "empty"/* Xml tag when callback is made */ + ) < 0) goto done; return &api; done: diff --git a/example/example_netconf.c b/example/example_netconf.c index f574de6f..dc8f2543 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -62,6 +62,19 @@ plugin_exit(clicon_handle h) return 0; } +/*! Local example netconf rpc callback + */ +int netconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + clicon_debug(1, "%s restconf", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static struct clixon_plugin_api api = { @@ -81,6 +94,9 @@ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, netconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; return &api; } diff --git a/example/example_restconf.c b/example/example_restconf.c index 380326ec..d65f9b19 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -266,6 +266,21 @@ plugin_credentials(clicon_handle h, goto done; } +/*! Local example restconf rpc callback + */ +int restconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) +{ + // FCGX_Request *r = (FCGX_Request *)arg; + clicon_debug(1, "%s", __FUNCTION__); + cprintf(cbret, "ok"); + return 0; +} + + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { @@ -285,5 +300,8 @@ clixon_plugin_api * clixon_plugin_init(clicon_handle h) { clicon_debug(1, "%s restconf", __FUNCTION__); + /* Register local netconf rpc client (note not backend rpc client) */ + if (rpc_callback_register(h, restconf_client_rpc, NULL, "client-rpc") < 0) + return NULL; return &api; } diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 82a44adc..2130134f 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -44,6 +44,15 @@ /* The dynamicically loadable plugin object handle */ typedef void *plghndl_t; +/* Registered RPC callback function */ +typedef int (*clicon_rpc_cb)( + clicon_handle h, /* Clicon handle */ + cxobj *xn, /* Request: */ + cbuf *cbret, /* Return xml tree, eg ..., rc_callback = cb; + rc->rc_arg = arg; + rc->rc_tag = strdup(tag); /* XXX strdup memleak */ + INSQ(rc, rpc_cb_list); + return 0; + done: + if (rc){ + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return -1; +} + +/*! Delete all RPC callbacks + */ +int +rpc_callback_delete_all(void) +{ + rpc_callback_t *rc; + + while((rc = rpc_cb_list) != NULL) { + DELQ(rc, rpc_cb_list, rpc_callback_t *); + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } + return 0; +} + +/*! 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: . + * @param[out] xret Return XML, error or OK + * @param[in] arg Domain-speific arg (eg client_entry) + * + * @retval -1 Error + * @retval 0 OK, not found handler. + * @retval 1 OK, handler called + */ +int +rpc_callback_call(clicon_handle h, + cxobj *xe, + cbuf *cbret, + void *arg) +{ + rpc_callback_t *rc; + int retval = -1; + + if (rpc_cb_list == NULL) + return 0; + rc = rpc_cb_list; + do { + if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ + if ((retval = rc->rc_callback(h, xe, cbret, arg, rc->rc_arg)) < 0){ + clicon_debug(1, "%s Error in: %s", __FUNCTION__, rc->rc_tag); + goto done; + } + else{ + retval = 1; /* handled */ + goto done; + } + } + rc = NEXTQ(rpc_callback_t *, rc); + } while (rc != rpc_cb_list); + retval = 0; + done: + return retval; +} diff --git a/test/test_netconf.sh b/test/test_netconf.sh index f94b7226..9877c770 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -25,7 +25,6 @@ cat < $cfg EOF -echo "clixon_backend -zf $cfg" # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg @@ -147,6 +146,9 @@ expecteof "$clixon_netconf -qf $cfg" " new "netconf rpc w/o namespace" expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +new "netconf client-side rpc" +expecteof "$clixon_netconf -qf $cfg" "example]]>]]>" "^ok]]>]]>$" + new "netconf subscription" expectwait "$clixon_netconf -qf $cfg" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 13586009..3dba5353 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -50,6 +50,19 @@ module example{ output { } } + rpc client-rpc { + description "Example local client-side rpc"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } } EOF @@ -72,7 +85,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D sleep 1 @@ -93,12 +106,12 @@ expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/r ' new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)" -expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}} +expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"ex:client-rpc": null,"rt:fib-route": null,"rt:route-count": null}} ' new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect="" match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -237,6 +250,15 @@ match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" fi + +new "restconf local client rpc using POST xml" +ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"request":"example"}}' http://localhost/restconf/operations/ex:client-rpc) +expect="ok" +match=`echo $ret | grep -EZo "$expect"` +if [ -z "$match" ]; then + err "$expect" "$ret" +fi + # XXX cant get -H to work #expecteq 'curl -s -X POST -H "Accept: application/yang-data+xml" -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' 'ipv42.3.4.5' From afb6aa31db1acd6dc763897c2e74baa062a9cad8 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 7 Apr 2018 19:04:56 +0200 Subject: [PATCH 31/50] Cleaned up apps/* Makefile.in:s --- apps/backend/Makefile.in | 28 +++++++++++++++------------ apps/cli/Makefile.in | 33 +++++++++++++++++++++----------- apps/netconf/Makefile.in | 36 ++++++++++++++++++++++------------- apps/restconf/Makefile.in | 40 ++++++++++++++++++++------------------- 4 files changed, 82 insertions(+), 55 deletions(-) diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 585bc059..2147bb81 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -64,23 +64,27 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LIB) -l CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -# Not accessible from plugin -APPSRC = backend_main.c backend_socket.c backend_client.c \ - backend_commit.c backend_plugin.c - -APPOBJ = $(APPSRC:.c=.o) +# Name of application APPL = clixon_backend -#SHLIB = clixon_backend -MYNAME = clixon_backend -MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) -MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) -MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) +# Not accessible from plugin +APPSRC = backend_main.c +APPSRC += backend_socket.c +APPSRC += backend_client.c +APPSRC += backend_commit.c +APPSRC += backend_plugin.c +APPOBJ = $(APPSRC:.c=.o) # Accessible from plugin LIBSRC = clixon_backend_transaction.c clixon_backend_handle.c LIBOBJ = $(LIBSRC:.c=.o) +# Name of lib +MYNAME = clixon_backend +MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) +MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) +MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) + all: $(MYLIB) $(APPL) test clean: @@ -127,7 +131,7 @@ test: test.c $(LIBOBJ) $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ $(MYLIB): $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) @@ -145,7 +149,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index 359038b0..78f9134a 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -68,22 +68,33 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_L CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ +# Name of application APPL = clixon_cli -SRC = cli_main.c -OBJS = $(SRC:.c=.o) +# Not accessible from plugin +APPSRC = cli_main.c +APPSRC += cli_generate.c +APPOBJ = $(APPSRC:.c=.o) + +# Accessible from plugin +LIBSRC = cli_common.c +LIBSRC += cli_show.c +LIBSRC += cli_handle.c +LIBSRC += cli_plugin.c +LIBOBJ = $(LIBSRC:.c=.o) + +# Name of lib MYNAME = clixon_cli MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) -LIBSRC = cli_plugin.c cli_common.c cli_show.c cli_handle.c cli_generate.c -LIBOBJS = $(LIBSRC:.c=.o) + all: $(MYLIB) $(APPL) test clean: - rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) + rm -f $(LIBOBJ) $(APPOBJ) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend test test.c @@ -125,14 +136,14 @@ test.c : test: test.c $(LIBOBJ) $(CC) $(INCLUDES) $(LDFLAGS) $< $(LIBOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(APPL): $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ +$(APPL): $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(MYLIB) : $(LIBOBJS) +$(MYLIB) : $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) - $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) else - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) endif # link-name is needed for application linking, eg for clixon_cli and clixon_config @@ -144,7 +155,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 17c09072..21b2d907 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -65,22 +65,32 @@ LIBS = -L$(top_srcdir)/lib/src @LIBS@ $(top_srcdir)/lib/src/$(CLIXON_LI CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -APPL = clixon_netconf -SRC = netconf_main.c -OBJS = $(SRC:.c=.o) +# Name of application +APPL = clixon_netconf +# Not accessible from plugin +APPSRC = netconf_main.c +APPSRC += netconf_hello.c +APPSRC += netconf_rpc.c +APPSRC += netconf_filter.c +APPOBJ = $(APPSRC:.c=.o) + +# Accessible from plugin +LIBSRC = netconf_lib.c + + +LIBOBJ = $(LIBSRC:.c=.o) + +# Name of lib MYNAME = clixon_netconf MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) -LIBSRC = netconf_hello.c netconf_rpc.c netconf_filter.c netconf_lib.c -LIBOBJS = $(LIBSRC:.c=.o) - all: $(MYLIB) $(APPL) clean: - rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) + rm -f $(APPL) $(APPOBJ) $(LIBOBJ) *.core $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend @@ -114,14 +124,14 @@ uninstall: .c.o: $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< -$(APPL) : $(OBJS) $(MYLIBLINK) $(LIBDEPS) - $(CC) $(LDFLAGS) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ +$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(MYLIB) : $(LIBOBJS) +$(MYLIB) : $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) - $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) else - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) endif # link-name is needed for application linking, eg for clixon_cli and clixon_config @@ -133,7 +143,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 1f7e3fff..e3b95cfb 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -66,26 +66,28 @@ CPPFLAGS = @CPPFLAGS@ -fPIC INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ -SRC = restconf_methods.c +# Name of application +APPL = clixon_restconf -OBJS = $(SRC:.c=.o) +# Not accessible from plugin +APPSRC = restconf_main.c +APPSRC += restconf_methods.c +APPOBJ = $(APPSRC:.c=.o) -APPSRC = restconf_main.c -APPOBJ = $(APPSRC:.c=.o) -APPL = clixon_restconf +# Accessible from plugin +LIBSRC = restconf_lib.c +LIBOBJ = $(LIBSRC:.c=.o) -MYNAME = clixon_restconf -MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) -MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) -MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) - -LIBSRC = restconf_lib.c -LIBOBJS = $(LIBSRC:.c=.o) +# Name of lib +MYNAME = clixon_restconf +MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX) +MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR) +MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR) all: $(MYLIB) $(APPL) clean: - rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(APPOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) + rm -f $(LIBOBJ) *.core $(APPL) $(APPOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK) distclean: clean rm -f Makefile *~ .depend @@ -118,14 +120,14 @@ uninstall: .c.o: $(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $< -$(APPL) : $(APPOBJ) $(MYLIBLINK) $(OBJS) $(LIBDEPS) - $(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@ +$(APPL) : $(APPOBJ) $(MYLIBLINK) $(LIBDEPS) + $(CC) $(LDFLAGS) $(APPOBJ) -L. $(MYLIB) $(LIBS) -o $@ -$(MYLIB) : $(LIBOBJS) +$(MYLIB) : $(LIBOBJ) ifeq ($(HOST_VENDOR),apple) - $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS) + $(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJ) $(LIBS) else - $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO) + $(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJ) $(LIBS) -Wl,-soname=$(MYLIBSO) endif # link-name is needed for application linking, eg for clixon_cli and clixon_config @@ -135,7 +137,7 @@ TAGS: find . -name '*.[chyl]' -print | etags - depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend + $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(LIBSRC) $(APPSRC) > .depend #include .depend From 2e0041162150058e70d95d65a7aba26bde756eb4 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 8 Apr 2018 11:32:43 +0200 Subject: [PATCH 32/50] CLI plugin API restructuring completed. Now all plugin APIs have the generic form documented in README and FAQ. --- CHANGELOG.md | 3 +- README.md | 2 +- apps/backend/backend_plugin.c | 18 +- apps/cli/Makefile.in | 4 +- apps/cli/cli_handle.c | 14 +- apps/cli/cli_handle.h | 8 +- apps/cli/cli_main.c | 2 +- apps/cli/cli_plugin.c | 290 +++++++++-------------------- apps/cli/cli_plugin.h | 39 +--- clixon.conf.cpp.cpp | 129 ------------- doc/FAQ.md | 43 +++-- example/README.md | 45 ++++- example/example_backend.c | 4 + example/example_cli.c | 39 ++-- example/example_restconf.c | 11 +- lib/clixon/clixon_options.h | 5 - lib/clixon/clixon_plugin.h | 79 ++++---- lib/src/clixon_plugin.c | 125 +++++-------- yang/clixon-config@2018-02-12.yang | 8 - 19 files changed, 317 insertions(+), 551 deletions(-) delete mode 100644 clixon.conf.cpp.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1171f9..03cd32ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ * CLI parse hook * CLICON_FIND_PLUGIN * clicon_valcb() - * backend system plugins (CLIXON_BACKEND_SYSDIR) + * CLIXON_BACKEND_SYSDIR + * CLIXON_CLI_SYSDIR * CLICON_MASTER_PLUGIN config variable * Example of migrating a backend plugin module: * Add all callbacks in a clixon_plugin_api struct diff --git a/README.md b/README.md index aff68c49..31043311 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Extending ========= Clixon provides a core system and can be used as-is using available Yang specifications. However, an application very quickly needs to -specialize functions. Clixon is extended by (most commonly) writing +specialize functions. Clixon is extended by writing plugins for cli and backend. Extensions for netconf and restconf are also available. diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 00025617..29c7a25e 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -106,7 +106,7 @@ clixon_plugin_reset(clicon_handle h, plgreset_t *resetfn; /* Plugin auth */ int retval = 1; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((resetfn = cp->cp_api.ca_reset) == NULL) continue; if ((retval = resetfn(h, db)) < 0) { @@ -150,7 +150,7 @@ clixon_plugin_statedata(clicon_handle h, clicon_err(OE_CFG, ENOENT, "XML tree expected"); goto done; } - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_statedata) == NULL) continue; if ((x = xml_new("config", NULL, NULL)) == NULL) @@ -246,7 +246,7 @@ plugin_transaction_begin(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_begin) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -274,7 +274,7 @@ plugin_transaction_validate(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_validate) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -303,7 +303,7 @@ plugin_transaction_complete(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_complete) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -349,7 +349,7 @@ plugin_transaction_revert(clicon_handle h, tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; - while ((cp = plugin_each_revert(cp, nr)) != NULL) { + while ((cp = plugin_each_revert(h, cp, nr)) != NULL) { if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -379,7 +379,7 @@ plugin_transaction_commit(clicon_handle h, trans_cb_t *fn; int i=0; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { i++; if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; @@ -409,7 +409,7 @@ plugin_transaction_end(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_end) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -436,7 +436,7 @@ plugin_transaction_abort(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(cp)) != NULL) { + while ((cp = plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_abort) == NULL) continue; fn(h, (transaction_data)td); /* dont abort on error */ diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index 78f9134a..bdfd9bb9 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -56,8 +56,6 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ # Use this clixon lib for linking CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR) -# Location of system plugins -CLIXON_CLI_SYSDIR = $(libdir)/clixon/plugins/cli # For dependency. A little strange that we rely on it being built in the src dir # even though it may exist in $(libdir). But the new version may not have been installed yet. @@ -127,7 +125,7 @@ uninstall: .SUFFIXES: .c .o .c.o: - $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" -DCLIXON_CLI_SYSDIR=\"$(CLIXON_CLI_SYSDIR)\" $(CFLAGS) -c $< + $(CC) $(INCLUDES) $(CPPFLAGS) -D__PROGRAM__=\"$(APPL)\" $(CFLAGS) -c $< # Just link test programs test.c : diff --git a/apps/cli/cli_handle.c b/apps/cli/cli_handle.c index 10762a41..1690a143 100644 --- a/apps/cli/cli_handle.c +++ b/apps/cli/cli_handle.c @@ -172,12 +172,22 @@ cli_parse_file(clicon_handle h, } int -cli_susp_hook(clicon_handle h, cli_susphook_t *fn) +cli_susp_hook(clicon_handle h, + cligen_susp_cb_t *fn) { cligen_handle ch = cligen(h); /* This assume first arg of fn can be treated as void* */ - return cligen_susp_hook(ch, (cligen_susp_cb_t*)fn); + return cligen_susp_hook(ch, fn); +} +int +cli_interrupt_hook(clicon_handle h, + cligen_interrupt_cb_t *fn) +{ + cligen_handle ch = cligen(h); + + /* This assume first arg of fn can be treated as void* */ + return cligen_interrupt_hook(ch, fn); } char * diff --git a/apps/cli/cli_handle.h b/apps/cli/cli_handle.h index 273d40e8..bbf66742 100644 --- a/apps/cli/cli_handle.h +++ b/apps/cli/cli_handle.h @@ -39,7 +39,7 @@ /* * Prototypes - * Internal prototypes. For exported functions see clicon_cli_api.h + * Internal prototypes. For exported functions see clixon_cli_api.h */ int cli_parse_file(clicon_handle h, FILE *f, @@ -47,7 +47,9 @@ int cli_parse_file(clicon_handle h, parse_tree *pt, cvec *globals); -int cli_susp_hook(clicon_handle h, cli_susphook_t *fn); +int cli_susp_hook(clicon_handle h, cligen_susp_cb_t *fn); + +int cli_interrupt_hook(clicon_handle h, cligen_interrupt_cb_t *fn); char *cli_nomatch(clicon_handle h); @@ -56,8 +58,8 @@ int cli_prompt_set(clicon_handle h, char *prompt); int cli_logsyntax_set(clicon_handle h, int status); /* Internal functions for handling cli groups */ - cli_syntax_t *cli_syntax(clicon_handle h); + int cli_syntax_set(clicon_handle h, cli_syntax_t *stx); #endif /* _CLI_HANDLE_H_ */ diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 8f691256..4845b171 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -460,7 +460,7 @@ main(int argc, char **argv) */ tmp = *(argv-1); *(argv-1) = argv0; - cli_plugin_start(h, argc+1, argv-1); + clixon_plugin_start(h, argc+1, argv-1); *(argv-1) = tmp; cligen_line_scrolling_set(cli_cligen(h), clicon_option_int(h,"CLICON_CLI_LINESCROLLING")); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index bc7f9904..196d9259 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -66,13 +66,6 @@ #include "cli_plugin.h" #include "cli_handle.h" -/*! Name of master plugin functions - * More in clicon_plugin.h - * @note not really used consider documenting or remove - */ -#define PLUGIN_PROMPT_HOOK "plugin_prompt_hook" -#define PLUGIN_SUSP_HOOK "plugin_susp_hook" - /* * * CLI PLUGIN INTERFACE, INTERNAL SECTION @@ -82,7 +75,9 @@ /*! Find syntax mode named 'mode'. Create if specified */ static cli_syntaxmode_t * -syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) +syntax_mode_find(cli_syntax_t *stx, + const char *mode, + int create) { cli_syntaxmode_t *m; @@ -111,40 +106,26 @@ syntax_mode_find(cli_syntax_t *stx, const char *mode, int create) return m; } -/*! Find plugin by name - */ -static struct cli_plugin * -plugin_find_cli(cli_syntax_t *stx, char *plgnam) -{ - struct cli_plugin *p; - - if ((p = stx->stx_plugins) != NULL) - do { - if (strcmp (p->cp_name, plgnam) == 0) - return p; - p = NEXTQ(struct cli_plugin *, p); - } while (p && p != stx->stx_plugins); - - return NULL; -} - /*! Generate parse tree for syntax mode + * @param[in] h Clicon handle + * @param[in] m Syntax mode struct */ static int -gen_parse_tree(clicon_handle h, cli_syntaxmode_t *m) +gen_parse_tree(clicon_handle h, + cli_syntaxmode_t *m) { cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt); return 0; } - /*! Append syntax + * @param[in] h Clicon handle */ static int syntax_append(clicon_handle h, cli_syntax_t *stx, - const char *name, - parse_tree pt) + const char *name, + parse_tree pt) { cli_syntaxmode_t *m; @@ -157,27 +138,18 @@ syntax_append(clicon_handle h, return 0; } -/*! Unload all plugins in a group +/*! Remove all cligen syntax modes + * @param[in] h Clicon handle */ static int cli_syntax_unload(clicon_handle h) { cli_syntax_t *stx = cli_syntax(h); - struct cli_plugin *p; cli_syntaxmode_t *m; if (stx == NULL) return 0; - while (stx->stx_nplugins > 0) { - p = stx->stx_plugins; - plugin_unload(h, p->cp_handle); - clicon_debug(1, "DEBUG: Plugin '%s' unloaded.", p->cp_name); - DELQ(p, stx->stx_plugins, struct cli_plugin *); - if (p) - free(p); - stx->stx_nplugins--; - } while (stx->stx_nmodes > 0) { m = stx->stx_modes; DELQ(m, stx->stx_modes, cli_syntaxmode_t *); @@ -237,34 +209,6 @@ clixon_str2fn(char *name, return NULL; } -/*! Load a dynamic plugin object and call it's init-function - * Note 'file' may be destructively modified - * @retval plugin-handle should be freed after use - */ -static plghndl_t -cli_plugin_load(clicon_handle h, - char *file, - int dlflags) -{ - char *name; - plghndl_t handle = NULL; - struct cli_plugin *cp = NULL; - - if ((handle = plugin_load(h, file, dlflags)) == NULL) - goto quit; - if ((cp = malloc(sizeof (struct cli_plugin))) == NULL) { - perror("malloc"); - goto quit; - } - memset (cp, 0, sizeof(*cp)); - name = basename(file); - snprintf(cp->cp_name, sizeof(cp->cp_name), "%.*s", (int)strlen(name)-3, name); - cp->cp_handle = handle; - -quit: - return cp; -} - /*! Append to syntax mode from file * @param[in] h Clixon handle * @param[in] filename Name of file where syntax is specified (in syntax-group dir) @@ -286,7 +230,7 @@ cli_load_syntax(clicon_handle h, char **vec = NULL; int i, nvec; char *plgnam; - struct cli_plugin *p; + clixon_plugin *cp; if (dir) snprintf(filepath, MAXPATHLEN-1, "%s/%s", dir, filename); @@ -316,8 +260,8 @@ cli_load_syntax(clicon_handle h, mode = cvec_find_str(cvv, "CLICON_MODE"); if (plgnam != NULL) { /* Find plugin for callback resolving */ - if ((p = plugin_find_cli (cli_syntax(h), plgnam)) != NULL) - handle = p->cp_handle; + if ((cp = plugin_find(h, plgnam)) != NULL) + handle = cp->cp_handle; if (handle == NULL){ clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s\n", plgnam, filename, plgnam, @@ -325,7 +269,6 @@ cli_load_syntax(clicon_handle h, goto done; } } - /* Resolve callback names to function pointers. */ if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){ clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)", @@ -343,7 +286,10 @@ cli_load_syntax(clicon_handle h, if ((vec = clicon_strsep(mode, ":", &nvec)) == NULL) goto done; for (i = 0; i < nvec; i++) { - if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) { + if (syntax_append(h, + cli_syntax(h), + vec[i], + pt) < 0) { goto done; } if (prompt) @@ -361,74 +307,7 @@ done: return retval; } -/*! Load plugins within a directory - */ -static int -cli_plugin_load_dir(clicon_handle h, - char *dir, - cli_syntax_t *stx) -{ - int i; - int ndp; - struct dirent *dp = NULL; - char *master_plugin; - char master[MAXPATHLEN]; - char filename[MAXPATHLEN]; - struct cli_plugin *cp; - struct stat st; - int retval = -1; - - /* Format master plugin path */ - if ((master_plugin = clicon_master_plugin(h)) == NULL){ - clicon_err(OE_PLUGIN, 0, "clicon_master_plugin option not set"); - goto quit; - } - snprintf(master, MAXPATHLEN-1, "%s.so", master_plugin); - - /* Get plugin objects names from plugin directory */ - ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG); - if (ndp < 0) - goto quit; - - /* Load master plugin first */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, master); - if (stat(filename, &st) == 0) { - clicon_debug(1, "DEBUG: Loading master plugin '%s'", master); - cp = cli_plugin_load(h, filename, RTLD_NOW|RTLD_GLOBAL); - if (cp == NULL) - goto quit; - /* Look up certain call-backs in master plugin */ - stx->stx_prompt_hook = - dlsym(cp->cp_handle, PLUGIN_PROMPT_HOOK); - stx->stx_susp_hook = - dlsym(cp->cp_handle, PLUGIN_SUSP_HOOK); - INSQ(cp, stx->stx_plugins); - stx->stx_nplugins++; - } - - /* Load the rest */ - for (i = 0; i < ndp; i++) { - if (strcmp (dp[i].d_name, master) == 0) - continue; /* Skip master now */ - snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name); - clicon_debug(1, "DEBUG: Loading plugin '%s'", dp[i].d_name); - - if ((cp = cli_plugin_load (h, filename, RTLD_NOW)) == NULL) - goto quit; - INSQ(cp, stx->stx_plugins); - stx->stx_nplugins++; - } - - retval = 0; - - quit: - if (dp) - free(dp); - return retval; -} - - -/*! Load a syntax group. +/*! Load a syntax group. Includes both CLI plugin and CLIgen spec syntax files. * @param[in] h Clicon handle */ int @@ -443,6 +322,9 @@ cli_syntax_load (clicon_handle h) struct dirent *dp = NULL; cli_syntax_t *stx; cli_syntaxmode_t *m; + cligen_susp_cb_t *fns = NULL; + cligen_interrupt_cb_t *fni = NULL; + clixon_plugin *cp; /* Syntax already loaded. XXX should we re-load?? */ if ((stx = cli_syntax(h)) != NULL) @@ -456,59 +338,62 @@ cli_syntax_load (clicon_handle h) /* Allocate plugin group object */ if ((stx = malloc(sizeof(*stx))) == NULL) { clicon_err(OE_UNIX, errno, "malloc"); - goto quit; + goto done; } memset (stx, 0, sizeof (*stx)); /* Zero out all */ cli_syntax_set(h, stx); - /* First load CLICON system plugins. CLIXON_CLI_SYSDIR is defined - in Makefile*/ - if (cli_plugin_load_dir(h, CLIXON_CLI_SYSDIR, stx) < 0) - goto quit; - - /* Then load application plugins */ - if (plugin_dir && cli_plugin_load_dir(h, plugin_dir, stx) < 0) - goto quit; - + /* Load cli plugins */ + if (plugin_dir && + clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir)< 0) + goto done; if (clispec_file){ if (cli_load_syntax(h, clispec_file, NULL) < 0) - goto quit; + goto done; } if (clispec_dir){ /* load syntaxfiles */ if ((ndp = clicon_file_dirent(clispec_dir, &dp, "(.cli)$", S_IFREG)) < 0) - goto quit; + goto done; /* Load the rest */ for (i = 0; i < ndp; i++) { clicon_debug(1, "DEBUG: Loading syntax '%.*s'", (int)strlen(dp[i].d_name)-4, dp[i].d_name); if (cli_load_syntax(h, dp[i].d_name, clispec_dir) < 0) - goto quit; + goto done; } } /* Did we successfully load any syntax modes? */ if (stx->stx_nmodes <= 0) { retval = 0; - goto quit; + goto done; } /* Parse syntax tree for all modes */ m = stx->stx_modes; do { if (gen_parse_tree(h, m) != 0) - goto quit; + goto done; m = NEXTQ(cli_syntaxmode_t *, m); } while (m && m != stx->stx_modes); - - /* Set callbacks into CLIgen */ - cli_susp_hook(h, cli_syntax(h)->stx_susp_hook); + /* Set susp and interrupt callbacks into CLIgen */ + cp = NULL; + while ((cp = plugin_each(h, cp)) != NULL) { + if (fns==NULL && (fns = cp->cp_api.ca_suspend) != NULL) + if (cli_susp_hook(h, fns) < 0) + goto done; + if (fni==NULL && (fni = cp->cp_api.ca_interrupt) != NULL) + if (cli_susp_hook(h, fns) < 0) + goto done; + } /* All good. We can now proudly return a new group */ retval = 0; -quit: +done: if (retval != 0) { + clixon_plugin_exit(h); cli_syntax_unload(h); cli_syntax_set(h, NULL); } @@ -517,34 +402,15 @@ quit: return retval; } -/*! Call plugin_start() in all plugins - */ -int -cli_plugin_start(clicon_handle h, int argc, char **argv) -{ - struct cli_plugin *p; - cli_syntax_t *stx; - plgstart_t *startfun; -// XXX int (*startfun)(clicon_handle, int, char **); - - stx = cli_syntax(h); - - if ((p = stx->stx_plugins) != NULL) - do { - startfun = dlsym(p->cp_handle, PLUGIN_START); - if (dlerror() == NULL) - startfun(h, argc, argv); - p = NEXTQ(struct cli_plugin *, p); - } while (p && p != stx->stx_plugins); - - return 0; -} - -/* +/*! Remove syntax modes and remove syntax + * @param[in] h Clicon handle */ int cli_plugin_finish(clicon_handle h) { + /* Remove all CLI plugins */ + clixon_plugin_exit(h); + /* Remove all cligen syntax modes */ cli_syntax_unload(h); cli_syntax_set(h, NULL); return 0; @@ -553,6 +419,7 @@ cli_plugin_finish(clicon_handle h) /*! Help function to print a meaningful error string. * Sometimes the libraries specify an error string, if so print that. * Otherwise just print 'command error'. + * @param[in] f File handler to write error to. */ int cli_handler_err(FILE *f) @@ -704,22 +571,30 @@ done: } /*! Read command from CLIgen's cliread() using current syntax mode. + * @param[in] h Clicon handle * @retval string char* buffer containing CLIgen command * @retval NULL Fatal error */ char * clicon_cliread(clicon_handle h) { - char *ret; - char *pfmt = NULL; + char *ret; + char *pfmt = NULL; cli_syntaxmode_t *mode; - cli_syntax_t *stx; - + cli_syntax_t *stx; + cli_prompthook_t *fn; + clixon_plugin *cp; + stx = cli_syntax(h); mode = stx->stx_active_mode; - - if (stx->stx_prompt_hook) - pfmt = stx->stx_prompt_hook(h, mode->csm_name); + /* Get prompt from plugin callback? */ + cp = NULL; + while ((cp = plugin_each(h, cp)) != NULL) { + if ((fn = cp->cp_api.ca_prompt) == NULL) + continue; + pfmt = fn(h, mode->csm_name); + break; + } if (clicon_quiet_mode(h)) cli_prompt_set(h, ""); else @@ -732,6 +607,7 @@ clicon_cliread(clicon_handle h) } /*! Initialize plugin code (not the plugins themselves) + * @param[in] h Clicon handle */ int cli_plugin_init(clicon_handle h) @@ -746,11 +622,12 @@ cli_plugin_init(clicon_handle h) */ -/* - * Set syntax mode mode for existing current plugin group. +/*! Set syntax mode mode for existing current plugin group. + * @param[in] h Clicon handle */ int -cli_set_syntax_mode(clicon_handle h, const char *name) +cli_set_syntax_mode(clicon_handle h, + const char *name) { cli_syntaxmode_t *mode; @@ -761,8 +638,8 @@ cli_set_syntax_mode(clicon_handle h, const char *name) return 1; } -/* - * Get syntax mode name +/*! Get syntax mode name + * @param[in] h Clicon handle */ char * cli_syntax_mode(clicon_handle h) @@ -774,14 +651,15 @@ cli_syntax_mode(clicon_handle h) return csm->csm_name; } -/* - * Callback from cli_set_prompt(). Set prompt format for syntax mode - * Arguments: - * name : Name of syntax mode - * prompt : Prompt format +/*! Callback from cli_set_prompt(). Set prompt format for syntax mode + * @param[in] h Clicon handle + * @param[in] name Name of syntax mode + * @param[in] prompt Prompt format */ int -cli_set_prompt(clicon_handle h, const char *name, const char *prompt) +cli_set_prompt(clicon_handle h, + const char *name, + const char *prompt) { cli_syntaxmode_t *m; @@ -793,9 +671,14 @@ cli_set_prompt(clicon_handle h, const char *name, const char *prompt) } /*! Format prompt + * @param[out] prompt Prompt string to be written + * @param[in] plen Length of prompt string + * @param[in] fmt Stdarg fmt string */ static int -prompt_fmt (char *prompt, size_t plen, char *fmt, ...) +prompt_fmt (char *prompt, + size_t plen, + char *fmt, ...) { va_list ap; char *s = fmt; @@ -849,6 +732,7 @@ done: } /*! Return a formatted prompt string + * @param[in] fmt Format string */ char * cli_prompt(char *fmt) diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index 081b3332..298e1675 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -43,51 +43,26 @@ /* clicon generic callback pointer */ typedef void (clicon_callback_t)(clicon_handle h); -/* clicon_set value callback */ -typedef int (cli_valcb_t)(cvec *vars, cg_var *cgv, cg_var *arg); - -/* specific to cli. For common see clicon_plugin.h */ -/* Hook to get prompt format before each getline */ -typedef char *(cli_prompthook_t)(clicon_handle, char *mode); - -/* Ctrl-Z hook from getline() */ -typedef int (cli_susphook_t)(clicon_handle, char *, int, int *); - -/* CLIgen parse failure hook. Retry other mode? */ -typedef char *(cli_parsehook_t)(clicon_handle, char *, char *); - +/* List of syntax modes */ typedef struct { - qelem_t csm_qelem; /* List header */ - char csm_name[256]; /* Syntax mode name */ - char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */ - int csm_nsyntax; /* Num syntax specs registered by plugin */ - parse_tree csm_pt; /* CLIgen parse tree */ + qelem_t csm_qelem; /* List header */ + char csm_name[256]; /* Syntax mode name */ + char csm_prompt[CLI_PROMPT_LEN]; /* Prompt for mode */ + int csm_nsyntax; /* Num syntax specs registered by plugin */ + parse_tree csm_pt; /* CLIgen parse tree */ } cli_syntaxmode_t; -/* A plugin list object */ -struct cli_plugin { - qelem_t cp_qelem; /* List header */ - char cp_name[256]; /* Plugin name */ - void *cp_handle; /* Dynamic object handle */ -}; - -/* Plugin group object */ +/* Plugin group object. Just a single object, not list. part of cli_handle */ typedef struct { - int stx_nplugins; /* Number of plugins */ - struct cli_plugin *stx_plugins; /* List of plugins */ int stx_nmodes; /* Number of syntax modes */ cli_syntaxmode_t *stx_active_mode; /* Current active syntax mode */ cli_syntaxmode_t *stx_modes; /* List of syntax modes */ - cli_prompthook_t *stx_prompt_hook; /* Prompt hook */ - cli_susphook_t *stx_susp_hook; /* Ctrl-Z hook from getline() */ } cli_syntax_t; void *clixon_str2fn(char *name, void *handle, char **error); -int cli_plugin_start(clicon_handle, int argc, char **argv); - int cli_plugin_init(clicon_handle h); int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr); diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp deleted file mode 100644 index feed22aa..00000000 --- a/clixon.conf.cpp.cpp +++ /dev/null @@ -1,129 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# 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 ***** -# - -# -# CLIXON options - Default values -# The origin of this file is run a _first_ time through a pre-processor at -# clixon make install time causing autoconf constants (such as "prefix" and -# "localstatedir") to be replaced with their installed values. -# It should be run a _second_ time as a part of installation of the application, -# in case clixon.mk is included in the application include file, and -# "$(APPNAME).conf" rule is accessed. -# -# See clicon_tutorial for more documentation - -# Location of configuration-file for default values (this file) -CLICON_CONFIGFILE sysconfdir/APPNAME.conf - -# Location of YANG module and submodule files. -CLICON_YANG_DIR prefix/share/APPNAME/yang - -# Main yang module or absolute filename. If module then search as follows: -# /[@] -# CLICON_YANG_MODULE_MAIN clicon - -# Option used to construct initial yang file: -# [@] -CLICON_YANG_MODULE_REVISION - -# Location of backend .so plugins -CLICON_BACKEND_DIR libdir/APPNAME/backend - -# Location of netconf (frontend) .so plugins -CLICON_NETCONF_DIR libdir/APPNAME/netconf - -# Location of restconf (frontend) .so plugins -CLICON_RESTCONF_DIR libdir/APPNAME/restconf - -# Location of cli frontend .so plugins -CLICON_CLI_DIR libdir/APPNAME/cli - -# Location of frontend .cli cligen spec files -CLICON_CLISPEC_DIR libdir/APPNAME/clispec - -# Enabled uses "startup" configuration on boot -CLICON_USE_STARTUP_CONFIG 0 - -# Address family for communicating with clixon_backend (UNIX|IPv4|IPv6) -CLICON_SOCK_FAMILY UNIX - -# If family above is AF_UNIX: Unix socket for communicating with clixon_backend -# If family above is AF_INET: IPv4 address -CLICON_SOCK localstatedir/APPNAME/APPNAME.sock - -# Inet socket port for communicating with clixon_backend (only IPv4|IPv6) -CLICON_SOCK_PORT 4535 - -# Process-id file -CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile - -# Group membership to access clixon_backend unix socket -# CLICON_SOCK_GROUP clicon - -# Set if all configuration changes are committed directly, commit command unnecessary -# CLICON_AUTOCOMMIT 0 - -# Name of master plugin (both frontend and backend). Master plugin has special -# callbacks for frontends. See clicon user manual for more info. -# CLICON_MASTER_PLUGIN master - -# Startup CLI mode. This should match the CLICON_MODE in your startup clispec file -# CLICON_CLI_MODE base - -# Generate code for CLI completion of existing db symbols. Add name="myspec" in -# datamodel spec and reference as @myspec. -# CLICON_CLI_GENMODEL 1 - -# Generate code for CLI completion of existing db symbols -# CLICON_CLI_GENMODEL_COMPLETION 1 - -# How to generate and show CLI syntax: VARS|ALL -# CLICON_CLI_GENMODEL_TYPE VARS - -# Directory where "running", "candidate" and "startup" are placed -CLICON_XMLDB_DIR localstatedir/APPNAME - -# XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch]) -CLICON_XMLDB_PLUGIN libdir/xmldb/text.so - -# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored -# CLICON_CLI_VARONLY 1 - -# Set to 0 if you want CLI to wrap to next line. -# Set to 1 if you want CLI to scroll sideways when approaching right margin -# CLICON_CLI_LINESCROLLING 1 - -# FastCGI unix socket. Should be specified in webserver -# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock; -CLICON_RESTCONF_PATH /www-data/fastcgi_restconf.sock - diff --git a/doc/FAQ.md b/doc/FAQ.md index 2b151e27..949f94ce 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -1,4 +1,4 @@ -# Clixon FAQ +i# Clixon FAQ ## What is Clixon? @@ -197,6 +197,7 @@ The second way is by programming the plugin_reset() in the backend plugin. The example code contains an example on how to do this (see plugin_reset() in example_backend.c). ## I want to program. How do I extend the example? +See [../apps/example] - example.xml - Change the configuration file - The yang specifications - This is the central part. It changes the XML, database and the config cli. - example_cli.cli - Change the fixed part of the CLI commands @@ -205,6 +206,25 @@ plugin. The example code contains an example on how to do this (see plugin_reset - example_netconf.c - Netconf plugin - example_restconf.c - Add restconf authentication, etc. +## How is a plugin initiated? +Each plugin is initiated with an API struct followed by a plugin init function as follows: +``` + static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, + plugin_start, + ... /* more functions here */ + } + clixon_plugin_api * + clixon_plugin_init(clicon_handle h) + { + ... + return &api; /* Return NULL on error */ + } +``` +For more info see [../example/README.md] + + ## How do I write a commit function? In the example, you write a commit function in example_backend.c. Every time a commit is made, transaction_commit() is called in the @@ -284,10 +304,10 @@ implement the RFC, you need to register an RPC callback in the backend plugin: Example: ``` int -plugin_init(clicon_handle h) +clixon_plugin_init(clicon_handle h) { ... - backend_rpc_cb_register(h, fib_route, NULL, "fib-route"); + rpc_callback_register(h, fib_route, NULL, "fib-route"); ... } ``` @@ -296,9 +316,9 @@ And then define the callback itself: static int fib_route(clicon_handle h, /* Clicon handle */ cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; @@ -313,13 +333,14 @@ You can specify an authentication callback for restconf as follows: ``` int plugin_credentials(clicon_handle h, - FCGX_Request *r, - char **username) + void *arg) +{ + FCGX_Request *r = (FCGX_Request *)arg; + ... + clicon_username_set(h, user); ``` -If a plugin is provided, it needs to supply a username. If not, the -request is unauthorized. the function mallocs a username and returns -it. +To authenticate, the callback needs to return the value 1 and supply a username. -See (../apps/example/example_restconf.c) plugin_credentials() for +See [../apps/example/example_restconf.c] plugin_credentials() for an example of HTTP basic auth. diff --git a/example/README.md b/example/README.md index 86bba2c9..eed8f40c 100644 --- a/example/README.md +++ b/example/README.md @@ -89,6 +89,43 @@ Routing notification ... ``` +## Initializing a plugin + +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. +``` +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 +}; + +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + /* Optional callback registration for RPC calls */ + rpc_callback_register(h, fib_route, NULL, "fib-route"); + /* Return plugin API */ + return &api; /* Return NULL on error */ +} +``` + ## Operation data Clixon implements Yang RPC operations by an extension mechanism. The @@ -119,18 +156,18 @@ In the backend, a callback is registered (fib_route()) which handles the RPC. static int fib_route(clicon_handle h, cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ cbuf *cbret, /* Reply eg ... */ - void *arg) /* Argument given at register */ + void *arg, /* Client session */ + void *regarg) /* Argument given at register */ { cprintf(cbret, ""); return 0; } int -plugin_init(clicon_handle h) +clixon_plugin_init(clicon_handle h) { ... - backend_rpc_cb_register(h, fib_route, NULL, "fib-route"); + rpc_callback_register(h, fib_route, NULL, "fib-route"); ... } ``` diff --git a/example/example_backend.c b/example/example_backend.c index da16ab0b..501fa7bf 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -256,6 +256,9 @@ static clixon_plugin_api api = { plugin_start, /* start */ NULL, /* exit */ NULL, /* auth */ + NULL, /* cli prompt */ + NULL, /* cli suspend */ + NULL, /* cli interrupt */ plugin_reset, /* reset */ plugin_statedata, /* statedata */ NULL, /* trans begin */ @@ -293,6 +296,7 @@ clixon_plugin_init(clicon_handle h) "empty"/* Xml tag when callback is made */ ) < 0) goto done; + /* Return plugin API */ return &api; done: return NULL; diff --git a/example/example_cli.c b/example/example_cli.c index 8610bd01..ddaa640b 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -52,19 +52,6 @@ #include #include -/* - * Plugin initialization - */ -int -plugin_init(clicon_handle h) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - srandom(tv.tv_usec); - - return 0; -} /*! Example cli function */ int @@ -125,3 +112,29 @@ fib_route_rpc(clicon_handle h, return retval; } +static clixon_plugin_api api = { + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + NULL, /* auth */ + NULL, /* cli_prompthook_t */ + NULL, /* cligen_susp_cb_t */ + NULL, /* cligen_interrupt_cb_t */ +}; + +/*! CLI plugin initialization + * @param[in] h Clixon handle + * @retval NULL Error with clicon_err set + * @retval api Pointer to API struct + */ +clixon_plugin_api * +clixon_plugin_init(clicon_handle h) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + + return &api; +} diff --git a/example/example_restconf.c b/example/example_restconf.c index d65f9b19..2f01fe98 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -268,11 +268,12 @@ plugin_credentials(clicon_handle h, /*! Local example restconf rpc callback */ -int restconf_client_rpc(clicon_handle h, - cxobj *xn, - cbuf *cbret, - void *arg, - void *regarg) +int +restconf_client_rpc(clicon_handle h, + cxobj *xn, + cbuf *cbret, + void *arg, + void *regarg) { // FCGX_Request *r = (FCGX_Request *)arg; clicon_debug(1, "%s", __FUNCTION__); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 490e152c..a1efe18a 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -43,8 +43,6 @@ */ /* default group membership to access config unix socket */ #define CLICON_SOCK_GROUP "clicon" -/* Default name of master plugin */ -#define CLICON_MASTER_PLUGIN "master" /* * Types @@ -137,9 +135,6 @@ static inline char *clicon_sock_group(clicon_handle h){ static inline char *clicon_backend_pidfile(clicon_handle h){ return clicon_option_str(h, "CLICON_BACKEND_PIDFILE"); } -static inline char *clicon_master_plugin(clicon_handle h){ - return clicon_option_str(h, "CLICON_MASTER_PLUGIN"); -} static inline char *clicon_xmldb_dir(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_DIR"); } diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 2130134f..e0281371 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -38,10 +38,16 @@ #ifndef _CLIXON_PLUGIN_H_ #define _CLIXON_PLUGIN_H_ +/* + * Constants + */ +/* Hardcoded plugin symbol. Must exist in all plugins to kickstart */ +#define CLIXON_PLUGIN_INIT "clixon_plugin_init" + /* * Types */ -/* The dynamicically loadable plugin object handle */ +/* Dynamicically loadable plugin object handle. @see return value of dlopen(3) */ typedef void *plghndl_t; /* Registered RPC callback function */ @@ -62,22 +68,13 @@ typedef int (*clicon_rpc_cb)( * Backend see config_plugin.c */ -/*! Called when plugin loaded. Only mandadory callback. All others optional - * @see plginit_t - */ -#define PLUGIN_INIT "plugin_init" - -typedef void * (plginit_t)(clicon_handle); /* Clixon plugin Init */ - /* Called when backend started with cmd-line arguments from daemon call. * @see plgstart_t */ -#define PLUGIN_START "plugin_start" typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */ /* Called just before plugin unloaded. */ -#define PLUGIN_EXIT "plugin_exit" typedef int (plgexit_t)(clicon_handle); /* Plugin exit */ /*! Called by restconf to check credentials and return username @@ -99,6 +96,14 @@ typedef void *transaction_data; /* Transaction callbacks */ typedef int (trans_cb_t)(clicon_handle h, transaction_data td); +/* Hook to override default prompt with explicit function + * Format prompt before each getline + * @param[in] h Clicon handle + * @param[in] mode Cligen syntax mode + * @retval prompt Prompt to prepend all CLigen command lines + */ +typedef char *(cli_prompthook_t)(clicon_handle, char *mode); + /* plugin init struct for the api * Note: Implicit init function */ @@ -106,14 +111,20 @@ struct clixon_plugin_api; typedef struct clixon_plugin_api* (plginit2_t)(clicon_handle); /* Clixon plugin Init */ struct clixon_plugin_api{ + /*--- Common fields. ---*/ char ca_name[PATH_MAX]; /* Name of plugin (given by plugin) */ plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ plgstart_t *ca_start; /* Plugin start */ plgexit_t *ca_exit; /* Plugin exit */ plgauth_t *ca_auth; /* Auth credentials */ - /*--Above here common fields w clixon_backend_api ----------*/ - plgreset_t *ca_reset; /* Reset system status (backend only) */ + /*--- CLI plugin-only ---*/ + cli_prompthook_t *ca_prompt; /* Prompt hook */ + cligen_susp_cb_t *ca_suspend; /* Ctrl-Z hook, see cligen getline */ + cligen_interrupt_cb_t *ca_interrupt; /* Ctrl-C, see cligen getline */ + + /*--- Backend plugin only ---*/ + plgreset_t *ca_reset; /* Reset system status (backend only) */ plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */ trans_cb_t *ca_trans_begin; /* Transaction start */ trans_cb_t *ca_trans_validate; /* Transaction validation */ @@ -124,46 +135,34 @@ struct clixon_plugin_api{ }; typedef struct clixon_plugin_api clixon_plugin_api; -/*! Called when plugin loaded. Only mandadory callback. All others optional - * @see plginit_t - */ - /* Internal plugin structure with dlopen() handle and plugin_api */ struct clixon_plugin{ - char cp_name[PATH_MAX]; /* Plugin filename. Note api ca_name is given by plugin itself */ - plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */ - struct clixon_plugin_api cp_api; + char cp_name[PATH_MAX]; /* Plugin filename. Note api ca_name is given by plugin itself */ + plghndl_t cp_handle; /* Handle to plugin using dlopen(3) */ + clixon_plugin_api cp_api; }; typedef struct clixon_plugin clixon_plugin; -/* - * Pseudo-Prototypes - * User-defineed plugins, not in library code - */ -#define CLIXON_PLUGIN_INIT "clixon_plugin_init" /* Nextgen */ - -/*! Plugin initialization - * @param[in] h Clixon handle - * @retval NULL Error with clicon_err set - * @retval api Pointer to API struct - */ -clixon_plugin_api *clixon_plugin_init(clicon_handle h); - /* * Prototypes */ -clixon_plugin *plugin_each(clixon_plugin *cpprev); -clixon_plugin *plugin_each_revert(clixon_plugin *cpprev, int nr); + +/*! Plugin initialization function. Must appear in all plugins + * @param[in] h Clixon handle + * @retval api Pointer to API struct + * @see CLIXON_PLUGIN_INIT default symbol + */ +clixon_plugin_api *clixon_plugin_init(clicon_handle h); + +clixon_plugin *plugin_each(clicon_handle h, clixon_plugin *cpprev); + +clixon_plugin *plugin_each_revert(clicon_handle h, clixon_plugin *cpprev, int nr); + +clixon_plugin *plugin_find(clicon_handle h, char *name); int clixon_plugins_load(clicon_handle h, char *function, char *dir); -/* obsolete */ -plghndl_t plugin_load (clicon_handle h, char *file, int dlflags); - -/* obsolete */ -int plugin_unload(clicon_handle h, plghndl_t *handle); - int clixon_plugin_start(clicon_handle h, int argc, char **argv); int clixon_plugin_exit(clicon_handle h); diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index ec442075..c0a37be5 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -60,7 +60,10 @@ #include "clixon_xml.h" #include "clixon_plugin.h" -/* XXX The below should be placed in clixon handle when done */ +/* List of plugins XXX + * 1. Place in clixon handle not global variables + * 2. Use qelem circular lists + */ static clixon_plugin *_clixon_plugins = NULL; /* List of plugins (of client) */ static int _clixon_nplugins = 0; /* Number of plugins */ @@ -69,17 +72,19 @@ static int _clixon_nplugins = 0; /* Number of plugins */ * @note Never manipulate the plugin during operation or using the * same object recursively * + * @param[in] h Clicon handle * @param[in] plugin previous plugin, or NULL on init * @code * clicon_plugin *cp = NULL; - * while ((cp = plugin_each(cp)) != NULL) { + * while ((cp = plugin_each(h, cp)) != NULL) { * ... * } * @endcode * @note Not optimized, alwasy iterates from the start of the list */ clixon_plugin * -plugin_each(clixon_plugin *cpprev) +plugin_each(clicon_handle h, + clixon_plugin *cpprev) { int i; clixon_plugin *cp; @@ -105,17 +110,19 @@ plugin_each(clixon_plugin *cpprev) * @note Never manipulate the plugin during operation or using the * same object recursively * + * @param[in] h Clicon handle * @param[in] plugin previous plugin, or NULL on init * @code * clicon_plugin *cp = NULL; - * while ((cp = plugin_each_revert(cp, nr)) != NULL) { + * while ((cp = plugin_each_revert(h, cp, nr)) != NULL) { * ... * } * @endcode * @note Not optimized, alwasy iterates from the start of the list */ clixon_plugin * -plugin_each_revert(clixon_plugin *cpprev, +plugin_each_revert(clicon_handle h, + clixon_plugin *cpprev, int nr) { int i; @@ -137,6 +144,27 @@ plugin_each_revert(clixon_plugin *cpprev, return cpnext; } +/*! Find plugin by name + * @param[in] h Clicon handle + * @param[in] name Plugin name + * @retval p Plugin if found + * @retval NULL Not found + */ +clixon_plugin * +plugin_find(clicon_handle h, + char *name) +{ + int i; + clixon_plugin *cp = NULL; + + for (i = 0; i < _clixon_nplugins; i++) { + cp = &_clixon_plugins[i]; + if (strcmp(cp->cp_name, name) == 0) + return cp; + } + return NULL; +} + /*! Load a dynamic plugin object and call its init-function * @param[in] h Clicon handle * @param[in] file Which plugin to load @@ -158,6 +186,7 @@ plugin_load_one(clicon_handle h, clixon_plugin_api *api = NULL; clixon_plugin *cp = NULL; char *name; + char *p; clicon_debug(1, "%s", __FUNCTION__); dlerror(); /* Clear any existing error */ @@ -187,10 +216,18 @@ plugin_load_one(clicon_handle h, clicon_err(OE_UNIX, errno, "malloc"); goto done; } + memset(cp, 0, sizeof(struct clixon_plugin)); cp->cp_handle = handle; + /* Extract string after last '/' in filename, if any */ name = strrchr(file, '/') ? strrchr(file, '/')+1 : file; + /* strip extension, eg .so from name */ + if ((p=strrchr(name, '.')) != NULL) + *p = '\0'; + /* Copy name to struct */ + memcpy(cp->cp_name, name, strlen(name)+1); + snprintf(cp->cp_name, sizeof(cp->cp_name), "%*s", - (int)strlen(name)-2, name); + (int)strlen(name), name); cp->cp_api = *api; clicon_debug(1, "%s", __FUNCTION__); done: @@ -246,80 +283,6 @@ done: return retval; } -/*! Load a dynamic plugin object and call its init-function - * Note 'file' may be destructively modified - * @param[in] h Clicon handle - * @param[in] file Which plugin to load - * @param[in] dlflags See man(3) dlopen - * @note OBSOLETE - */ -plghndl_t -plugin_load(clicon_handle h, - char *file, - int dlflags) -{ - char *error; - void *handle = NULL; - plginit_t *initfn; - - clicon_debug(1, "%s", __FUNCTION__); - dlerror(); /* Clear any existing error */ - if ((handle = dlopen(file, dlflags)) == NULL) { - error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlopen: %s\n", error ? error : "Unknown error"); - goto done; - } - /* call plugin_init() if defined */ - if ((initfn = dlsym(handle, PLUGIN_INIT)) == NULL){ - clicon_err(OE_PLUGIN, errno, "Failed to find plugin_init when loading clixon plugin %s", file); - goto err; - } - if ((error = (char*)dlerror()) != NULL) { - clicon_err(OE_UNIX, 0, "dlsym: %s: %s", file, error); - goto done; - } - if (initfn(h) != 0) { - clicon_err(OE_PLUGIN, errno, "Failed to initiate %s", strrchr(file,'/')?strchr(file, '/'):file); - if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ - clicon_err(OE_DB, 0, "Unknown error: %s: plugin_init does not make clicon_err call on error", - file); - goto err; - } - done: - return handle; - err: - if (handle) - dlclose(handle); - return NULL; -} - -/*! Unload a plugin - * @param[in] h Clicon handle - * @param[in] handle Clicon handle - * @note OBSOLETE - */ -int -plugin_unload(clicon_handle h, - plghndl_t *handle) -{ - int retval = 0; - char *error; - plgexit_t *exitfn; - - /* Call exit function is it exists */ - exitfn = dlsym(handle, PLUGIN_EXIT); - if (dlerror() == NULL) - exitfn(h); - - dlerror(); /* Clear any existing error */ - if (dlclose(handle) != 0) { - error = (char*)dlerror(); - clicon_err(OE_PLUGIN, errno, "dlclose: %s\n", error ? error : "Unknown error"); - /* Just report */ - } - return retval; -} - /*! Call plugin_start in all plugins * @param[in] h Clicon handle */ @@ -425,7 +388,7 @@ clixon_plugin_auth(clicon_handle h, */ typedef struct { qelem_t rc_qelem; /* List header */ - clicon_rpc_cb rc_callback; /* RPC Callback */ + clicon_rpc_cb rc_callback; /* RPC Callback */ void *rc_arg; /* Application specific argument to cb */ char *rc_tag; /* Xml/json tag when matched, callback called */ } rpc_callback_t; diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index d710fae3..c5c80e4b 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -237,14 +237,6 @@ module clixon-config { "Set if all configuration changes are committed automatically on every edit change. Explicit commit commands unnecessary"; } - leaf CLICON_MASTER_PLUGIN { - type string; - default "master"; - description - "Name of master plugin (cli, netconf, restconf and backend). - Master plugin has special callbacks for frontends. - See clicon user manual for more info. (Obsolete?)"; - } leaf CLICON_XMLDB_DIR { type string; mandatory true; From 0907574acff974f2d692a0a87f7082c2ec588ce8 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sun, 8 Apr 2018 14:10:06 +0000 Subject: [PATCH 33/50] renamed plugin_find/each to clixon_plugin_find/each --- CHANGELOG.md | 5 ++++- apps/backend/backend_plugin.c | 18 +++++++++--------- apps/cli/cli_plugin.c | 6 +++--- lib/clixon/clixon_plugin.h | 25 ++++++++++++++++--------- lib/src/clixon_plugin.c | 18 +++++++++--------- 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03cd32ab..647a055e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Major changes: * Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 - * New design with single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. + * New design change `plugin_init()` to a single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. * Plugin RPC callback interface have been unified between backend, netconf and restconf. * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` @@ -45,6 +45,9 @@ static clixon_plugin_api api = { plugin_start, plugin_exit, NULL, /* auth N/A for backend */ + NULL, /* cli_prompthook_t */ + NULL, /* cligen_susp_cb_t */ + NULL, /* cligen_interrupt_cb_t */ plugin_reset, plugin_statedata, transaction_begin, diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 29c7a25e..ecc4b587 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -106,7 +106,7 @@ clixon_plugin_reset(clicon_handle h, plgreset_t *resetfn; /* Plugin auth */ int retval = 1; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((resetfn = cp->cp_api.ca_reset) == NULL) continue; if ((retval = resetfn(h, db)) < 0) { @@ -150,7 +150,7 @@ clixon_plugin_statedata(clicon_handle h, clicon_err(OE_CFG, ENOENT, "XML tree expected"); goto done; } - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_statedata) == NULL) continue; if ((x = xml_new("config", NULL, NULL)) == NULL) @@ -246,7 +246,7 @@ plugin_transaction_begin(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_begin) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -274,7 +274,7 @@ plugin_transaction_validate(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_validate) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -303,7 +303,7 @@ plugin_transaction_complete(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_complete) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -349,7 +349,7 @@ plugin_transaction_revert(clicon_handle h, tr.td_scvec = td->td_tcvec; tr.td_tcvec = td->td_scvec; - while ((cp = plugin_each_revert(h, cp, nr)) != NULL) { + while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -379,7 +379,7 @@ plugin_transaction_commit(clicon_handle h, trans_cb_t *fn; int i=0; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { i++; if ((fn = cp->cp_api.ca_trans_commit) == NULL) continue; @@ -409,7 +409,7 @@ plugin_transaction_end(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_end) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ @@ -436,7 +436,7 @@ plugin_transaction_abort(clicon_handle h, clixon_plugin *cp = NULL; trans_cb_t *fn; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_trans_abort) == NULL) continue; fn(h, (transaction_data)td); /* dont abort on error */ diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 196d9259..81ca2e9e 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -260,7 +260,7 @@ cli_load_syntax(clicon_handle h, mode = cvec_find_str(cvv, "CLICON_MODE"); if (plgnam != NULL) { /* Find plugin for callback resolving */ - if ((cp = plugin_find(h, plgnam)) != NULL) + if ((cp = clixon_plugin_find(h, plgnam)) != NULL) handle = cp->cp_handle; if (handle == NULL){ clicon_err(OE_PLUGIN, 0, "CLICON_PLUGIN set to '%s' in %s but plugin %s.so not found in %s\n", @@ -379,7 +379,7 @@ cli_syntax_load (clicon_handle h) /* Set susp and interrupt callbacks into CLIgen */ cp = NULL; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (fns==NULL && (fns = cp->cp_api.ca_suspend) != NULL) if (cli_susp_hook(h, fns) < 0) goto done; @@ -589,7 +589,7 @@ clicon_cliread(clicon_handle h) mode = stx->stx_active_mode; /* Get prompt from plugin callback? */ cp = NULL; - while ((cp = plugin_each(h, cp)) != NULL) { + while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((fn = cp->cp_api.ca_prompt) == NULL) continue; pfmt = fn(h, mode->csm_name); diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index e0281371..536b0c86 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -50,13 +50,19 @@ /* Dynamicically loadable plugin object handle. @see return value of dlopen(3) */ typedef void *plghndl_t; -/* Registered RPC callback function */ +/*! Registered RPC callback function + * @param[in] h Clicon handle + * @param[in] xn Request: + * @param[out] cbret Return xml tree, eg ..., */ - cbuf *cbret, /* Return xml tree, eg ..., Date: Sun, 8 Apr 2018 16:07:24 +0000 Subject: [PATCH 34/50] mv api_return_err to restconf_lib.c --- apps/restconf/clixon_restconf.h | 9 ++-- apps/restconf/restconf_lib.c | 79 ++++++++++++++++++++++++++++++++ apps/restconf/restconf_lib.h | 9 ++-- apps/restconf/restconf_methods.c | 78 ------------------------------- 4 files changed, 85 insertions(+), 90 deletions(-) diff --git a/apps/restconf/clixon_restconf.h b/apps/restconf/clixon_restconf.h index 554458f3..2c79c3cc 100644 --- a/apps/restconf/clixon_restconf.h +++ b/apps/restconf/clixon_restconf.h @@ -39,12 +39,7 @@ #define _CLIXON_RESTCONF_H_ /* - * Constants - */ - -/* - * Prototypes - * (Duplicated. Also in restconf_*.h) + * Prototypes (also in restconf_lib.h) */ int restconf_err2code(char *tag); const char *restconf_code2reason(int code); @@ -61,6 +56,8 @@ int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); int get_user_cookie(char *cookiestr, char *attribute, char **val); +int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, + int pretty, int use_xml); #endif /* _CLIXON_RESTCONF_H_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 211a3e14..d42a63ac 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -384,3 +384,82 @@ get_user_cookie(char *cookiestr, cvec_free(cvv); return retval; } + +/*! Return error on get/head request + * @param[in] h Clixon handle + * @param[in] r Fastcgi request handle + * @param[in] xerr XML error message from backend + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + */ +int +api_return_err(clicon_handle h, + FCGX_Request *r, + cxobj *xerr, + int pretty, + int use_xml) +{ + int retval = -1; + cbuf *cb = NULL; + cxobj *xtag; + char *tagstr; + int code; + const char *reason_phrase; + + clicon_debug(1, "%s", __FUNCTION__); + if ((cb = cbuf_new()) == NULL) + goto done; + if ((xtag = xpath_first(xerr, "error-tag")) == NULL){ + notfound(r); /* bad reply? */ + goto ok; + } + tagstr = xml_body(xtag); + code = restconf_err2code(tagstr); + if ((reason_phrase = restconf_code2reason(code)) == NULL) + reason_phrase=""; + if (xml_name_set(xerr, "error") < 0) + goto done; + if (use_xml){ + if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0) + goto done; + } + else + if (xml2json_cbuf(cb, xerr, pretty) < 0) + goto done; + FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", + use_xml?"xml":"json"); + if (use_xml){ + if (pretty){ + FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, " \r\n"); + } + else { + FCGX_FPrintF(r->out, "", cbuf_get(cb)); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "\r\n"); + } + } + else{ + if (pretty){ + FCGX_FPrintF(r->out, "{\n"); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", + cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } + else{ + FCGX_FPrintF(r->out, "{"); + FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); + FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); + FCGX_FPrintF(r->out, "}\r\n"); + } + } + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + if (cb) + cbuf_free(cb); + return retval; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 8162ff9c..cf8ef66b 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -37,11 +37,7 @@ #define _RESTCONF_LIB_H_ /* - * Constants - */ - -/* - * Prototypes + * Prototypes (also in clixon_restconf.h) */ int restconf_err2code(char *tag); const char *restconf_code2reason(int code); @@ -58,6 +54,7 @@ int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); cbuf *readdata(FCGX_Request *r); int get_user_cookie(char *cookiestr, char *attribute, char **val); - +int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, + int pretty, int use_xml); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index e28bcb89..49ea3efa 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -141,84 +141,6 @@ api_data_options(clicon_handle h, return 0; } -/*! Return error on get/head request - * @param[in] h Clixon handle - * @param[in] r Fastcgi request handle - * @param[in] xerr XML error message from backend - * @param[in] pretty Set to 1 for pretty-printed xml/json output - * @param[in] use_xml Set to 0 for JSON and 1 for XML - */ -static int -api_return_err(clicon_handle h, - FCGX_Request *r, - cxobj *xerr, - int pretty, - int use_xml) -{ - int retval = -1; - cbuf *cb = NULL; - cxobj *xtag; - char *tagstr; - int code; - const char *reason_phrase; - - clicon_debug(1, "%s", __FUNCTION__); - if ((cb = cbuf_new()) == NULL) - goto done; - if ((xtag = xpath_first(xerr, "error-tag")) == NULL){ - notfound(r); /* bad reply? */ - goto ok; - } - tagstr = xml_body(xtag); - code = restconf_err2code(tagstr); - if ((reason_phrase = restconf_code2reason(code)) == NULL) - reason_phrase=""; - if (xml_name_set(xerr, "error") < 0) - goto done; - if (use_xml){ - if (clicon_xml2cbuf(cb, xerr, 2, pretty) < 0) - goto done; - } - else - if (xml2json_cbuf(cb, xerr, pretty) < 0) - goto done; - FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); - FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n\r\n", - use_xml?"xml":"json"); - if (use_xml){ - if (pretty){ - FCGX_FPrintF(r->out, " \n", cbuf_get(cb)); - FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, " \r\n"); - } - else { - FCGX_FPrintF(r->out, "", cbuf_get(cb)); - FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, "\r\n"); - } - } - else{ - if (pretty){ - FCGX_FPrintF(r->out, "{\n"); - FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : %s\n", - cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\r\n"); - } - else{ - FCGX_FPrintF(r->out, "{"); - FCGX_FPrintF(r->out, "\"ietf-restconf:errors\" : "); - FCGX_FPrintF(r->out, "%s", cbuf_get(cb)); - FCGX_FPrintF(r->out, "}\r\n"); - } - } - ok: - retval = 0; - done: - clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); - if (cb) - cbuf_free(cb); - return retval; -} /*! Generic GET (both HEAD and GET) * According to restconf From 24de61f14a7b1c0f3092192f828a1b676f7ef3ba Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Mon, 9 Apr 2018 04:24:22 +0000 Subject: [PATCH 35/50] Ignore \r - common http/restconf end-of-line --- lib/src/clixon_json_parse.l | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/clixon_json_parse.l b/lib/src/clixon_json_parse.l index ea5eec30..3763d7f1 100644 --- a/lib/src/clixon_json_parse.l +++ b/lib/src/clixon_json_parse.l @@ -89,6 +89,7 @@ exp ({integer}|{real})[eE][+-]{integer} %% [ \t] \n { _JY->jy_linenum++; } +\r { } <> { return J_EOF; } \{ { return *yytext; } \} { return *yytext; } From ea792a78501386eddb92f5da43263d7a7564bf7e Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 9 Apr 2018 06:27:15 +0200 Subject: [PATCH 36/50] CHANGELOG typos --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 647a055e..ea9d84ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,7 +89,7 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h) clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) -* Experimental: Added CLICON_TRANSACTION_MOD configurqation option. If set, +* Experimental: Added CLICON_TRANSACTION_MOD configuration option. If set, modifications in validation and commit callbacks are written back into the datastore. * Invalid key to api_path2xml gives warning instead of error and quit. @@ -116,7 +116,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. ## 3.5.0 (12 February 2018) ### Major changes: -* Major Restconf feature update to comply to RFC 8040. Thanks Stephen Jones for getting right. +* Major Restconf feature update to comply to RFC 8040. Thanks Stephen Jones of Netgate for getting right. * GET: Always return object referenced (and nothing else). ie, GET /restconf/data/X returns X. * GET Added support for the following resources: Well-known, top-level resource, and yang library version, * GET Single element JSON lists use {list:[element]}, not {list:element}. From b2abab71f0cb83c1afdedd709e6a04f74323198d Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Sun, 15 Apr 2018 10:52:52 +0100 Subject: [PATCH 37/50] local restconf rpc callback sent error and then main restconf handler sent ok. Callback should just return xml buffer, not do actual restconf callback --- apps/restconf/restconf_lib.c | 2 +- apps/restconf/restconf_methods.c | 18 +++++++++++++++--- lib/src/clixon_plugin.c | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index d42a63ac..9f07985c 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -385,7 +385,7 @@ get_user_cookie(char *cookiestr, return retval; } -/*! Return error on get/head request +/*! Return restconf error on get/head request * @param[in] h Clixon handle * @param[in] r Fastcgi request handle * @param[in] xerr XML error message from backend diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 49ea3efa..22b3c322 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -984,6 +984,7 @@ api_operations_post(clicon_handle h, yang_stmt *youtput; cxobj *xdata = NULL; cxobj *xret = NULL; + cxobj *xerr; cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xe; @@ -1080,6 +1081,7 @@ api_operations_post(clicon_handle h, goto done; } xe = NULL; + while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { /* Look for local (client-side) restconf plugins. */ if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) @@ -1087,6 +1089,12 @@ api_operations_post(clicon_handle h, if (ret == 1){ /* Handled locally */ if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; + /* Local error: return it and quit */ + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } } break; /* Just one if local */ } @@ -1095,11 +1103,13 @@ api_operations_post(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; - xoutput=xpath_first(xret, "/"); - xml_name_set(xoutput, "output"); + if ((xoutput=xpath_first(xret, "/")) != NULL) + xml_name_set(xoutput, "output"); if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL && xoutput){ - // clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); +#if 0 + clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); +#endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) @@ -1122,7 +1132,9 @@ api_operations_post(clicon_handle h, else if (xml2json_cbuf(cbx, xoutput, pretty) < 0) goto done; +#if 1 clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); +#endif FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); } diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 1e231b45..3675b37b 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -465,8 +465,8 @@ rpc_callback_call(clicon_handle h, cbuf *cbret, void *arg) { - rpc_callback_t *rc; int retval = -1; + rpc_callback_t *rc; if (rpc_cb_list == NULL) return 0; @@ -486,5 +486,6 @@ rpc_callback_call(clicon_handle h, } while (rc != rpc_cb_list); retval = 0; done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } From 765080347589e037998b48e298f0aa1af246e9cf Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 19 Apr 2018 22:44:15 +0200 Subject: [PATCH 38/50] * Experimental NACM RFC8341 Network Configuration Access Control Model. * CLICON_NACM_MODE config option, default is disabled. * Added username attribute to all rpc:s from frontend to backend * Added NACM backend module in example --- CHANGELOG.md | 28 +- apps/backend/backend_client.c | 264 +++++++++++++++++- apps/backend/backend_main.c | 15 +- apps/backend/backend_plugin.c | 38 ++- apps/backend/backend_plugin.h | 3 +- apps/cli/cli_main.c | 14 +- apps/cli/cli_plugin.c | 11 +- apps/cli/cli_plugin.h | 2 - apps/cli/cli_show.c | 11 +- apps/netconf/netconf_main.c | 16 +- apps/restconf/restconf_lib.c | 6 +- apps/restconf/restconf_main.c | 85 +++--- apps/restconf/restconf_methods.c | 91 +++--- datastore/text/clixon_xmldb_text.c | 6 +- example/Makefile.in | 6 +- example/example.yang | 35 ++- example/example_backend.c | 22 +- ...end_secondary.c => example_backend_nacm.c} | 52 ++-- example/example_cli.c | 7 +- example/example_netconf.c | 3 +- example/example_restconf.c | 26 +- lib/clixon/clixon_netconf_lib.h | 2 + lib/clixon/clixon_plugin.h | 53 ++-- lib/clixon/clixon_xml_map.h | 2 +- lib/src/clixon_file.c | 8 +- lib/src/clixon_netconf_lib.c | 68 ++++- lib/src/clixon_plugin.c | 7 +- lib/src/clixon_proto_client.c | 58 +++- lib/src/clixon_xml_map.c | 54 +++- test/test_auth.sh | 160 ++++++++++- test/test_restconf.sh | 1 + yang/clixon-config@2018-02-12.yang | 28 ++ 32 files changed, 908 insertions(+), 274 deletions(-) rename example/{example_backend_secondary.c => example_backend_nacm.c} (67%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9d84ec..36450023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ ## 3.6.0 (Upcoming) ### Major changes: -* Restructure and more generic plugin API (cli,backend,restconf,netconf) as preparation for authorization RFC8341 +* Experimental NACM RFC8341 Network Configuration Access Control Model. + * CLICON_NACM_MODE config option, default is disabled. + * Added username attribute to all rpc:s from frontend to backend + * Added NACM backend module in example +* Restructure and more generic plugin API (cli,backend,restconf,netconf). * New design change `plugin_init()` to a single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. * Plugin RPC callback interface have been unified between backend, netconf and restconf. * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` @@ -11,6 +15,7 @@ * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. * Moved specific plugin functions from apps/ to generic functions in lib/ + * New config option CLICON_BACKEND_REGEXP to match backkend plugins (if you do not all loaded). * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() * Removed some obscure plugin code that seem not to be used (please report if needed!) @@ -40,22 +45,11 @@ plugin_init(clicon_handle h) clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { - "example", /* name */ - clixon_plugin_init, - plugin_start, - plugin_exit, - NULL, /* auth N/A for backend */ - NULL, /* cli_prompthook_t */ - NULL, /* cligen_susp_cb_t */ - NULL, /* cligen_interrupt_cb_t */ - plugin_reset, - plugin_statedata, - transaction_begin, - transaction_validate, - transaction_complete, - transaction_commit, - transaction_end, - transaction_abort + "example", /* name */ + clixon_plugin_init, /* init */ + NULL, /* start */ + NULL, /* exit */ + .ca_auth=plugin_credentials /* restconf specific: auth */ }; clixon_plugin_api *clixon_plugin_init(clicon_handle h) diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 97b583da..b21611fc 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -347,7 +347,7 @@ from_client_edit_config(clicon_handle h, cbuf *cbx = NULL; /* Assist cbuf */ if ((yspec = clicon_dbspec_yang(h)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); + clicon_err(OE_YANG, ENOENT, "No yang spec9"); goto done; } if ((target = netconf_db_find(xn, "target")) == NULL){ @@ -803,7 +803,253 @@ from_client_debug(clicon_handle h, return retval; } +/*! Match nacm access operations according to RFC8321 3.4.4. + * Incoming RPC Message Validation Step 7 (c) + * The rule's "access-operations" leaf has the "exec" bit set or + * has the special value "*". + * @retval 0 No match + * @retval 1 Match + */ +static int +nacm_match_access(char *access_operations, + char *mode) +{ + if (access_operations==NULL) + return 0; + if (strcmp(access_operations,"*")==0) + return 1; + if (strstr(mode, access_operations)!=NULL) + return 1; + return 0; +} + +/*! Match nacm single rule. Either match with access or deny. Or not match. + * @param[in] h Clicon handle + * @param[in] name rpc name + * @param[in] xrule NACM rule XML tree + * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. + * @retval -1 Error + * @retval 0 Matching rule AND Not access and cbret set + * @retval 1 Matchung rule AND Access + * @retval 2 No matching rule Goto step 10 + * From RFC8321 3.4.4. Incoming RPC Message Validation + +---------+-----------------+---------------------+-----------------+ + | Method | Resource class | NETCONF operation | Access | + | | | | operation | + +---------+-----------------+---------------------+-----------------+ + | OPTIONS | all | none | none | + | HEAD | all | , | read | + | GET | all | , | read | + | POST | datastore, data | | create | + | POST | operation | specified operation | execute | + | PUT | data | | create, update | + | PUT | datastore | | update | + | PATCH | data, datastore | | update | + | DELETE | data | | delete | + + 7.(cont) A rule matches if all of the following criteria are met: + * The rule's "module-name" leaf is "*" or equals the name of + the YANG module where the protocol operation is defined. + + * Either (1) the rule does not have a "rule-type" defined or + (2) the "rule-type" is "protocol-operation" and the + "rpc-name" is "*" or equals the name of the requested + protocol operation. + + * The rule's "access-operations" leaf has the "exec" bit set or + has the special value "*". + */ +static int +nacm_match_rule(clicon_handle h, + char *name, + cxobj *xrule, + cbuf *cbret) +{ + int retval = -1; + // cxobj *x; + char *module_name; + char *rpc_name; + char *access_operations; + char *action; + + module_name = xml_find_body(xrule, "module-name"); + rpc_name = xml_find_body(xrule, "rpc-name"); + access_operations = xml_find_body(xrule, "access-operations"); + action = xml_find_body(xrule, "action"); + clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__, + module_name, rpc_name, access_operations, action); + if (module_name && strcmp(module_name,"*")==0){ + if (nacm_match_access(access_operations, "exec")){ + if (rpc_name==NULL || + strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){ + /* Here is a matching rule */ + if (action && strcmp(action, "permit")==0){ + retval = 1; + goto done; + } + else{ + if (netconf_access_denied(cbret, "protocol", "access denied") < 0) + goto done; + retval = 0; + goto done; + } + } + } + } + retval = 2; /* no matching rule */ + done: + return retval; + +} + +/*! Make nacm access control + * @param[in] h Clicon handle + * @param[in] name rpc name + * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. + * @retval -1 Error + * @retval 0 Not access and cbret set + * @retval 1 Access + * From RFC8321 3.4.4. Incoming RPC Message Validation + */ +static int +nacm_access(clicon_handle h, + char *name, + char *username, + cbuf *cbret) +{ + int retval = -1; + cxobj *xtop = NULL; + cxobj *xacm; + cxobj *x; + cxobj *xrlist; + cxobj *xrule; + char *enabled = NULL; + cxobj **gvec = NULL; /* groups */ + size_t glen; + cxobj **rlistvec = NULL; /* rule-list */ + size_t rlistlen; + cxobj **rvec = NULL; /* rules */ + size_t rlen; + int i, j; + char *exec_default = NULL; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + /* 1. If the "enable-nacm" leaf is set to "false", then the protocol + operation is permitted. (or config does not exist) */ + if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) + goto done; + if ((xacm = xpath_first(xtop, "nacm")) == NULL) + goto permit; + exec_default = xml_find_body(xacm, "exec-default"); + if ((x = xpath_first(xacm, "enable-nacm")) == NULL) + goto permit; + enabled = xml_body(x); + if (strcmp(enabled, "true") != 0) + goto permit; + + /* 2. If the requesting session is identified as a recovery session, + then the protocol operation is permitted. NYI */ + + /* 3. If the requested operation is the NETCONF + protocol operation, then the protocol operation is permitted. + */ + if (strcmp(name, "close-session") == 0) + goto permit; + /* 4. Check all the "group" entries to see if any of them contain a + "user-name" entry that equals the username for the session + making the request. (If the "enable-external-groups" leaf is + "true", add to these groups the set of groups provided by the + transport layer.) */ + if (username == NULL) + goto step10; + /* User's group */ + if (xpath_vec(xacm, "groups/group[user-name=%s]", &gvec, &glen, username) < 0) + goto done; + /* 5. If no groups are found, continue with step 10. */ + if (glen == 0) + goto step10; + /* 6. Process all rule-list entries, in the order they appear in the + configuration. If a rule-list's "group" leaf-list does not + match any of the user's groups, proceed to the next rule-list + entry. */ + if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0) + goto done; + for (i=0; i or , then the protocol operation + is denied. */ + if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){ + if (netconf_access_denied(cbret, "protocol", "default deny") < 0) + goto done; + goto deny; + } + /* 12. If the "exec-default" leaf is set to "permit", then permit the + protocol operation; otherwise, deny the request. */ + if (exec_default ==NULL || strcmp(exec_default, "permit")==0) + goto permit; + if (netconf_access_denied(cbret, "protocol", "default deny") < 0) + goto done; + goto deny; + permit: + retval = 1; + done: + clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); + if (xtop) + xml_free(xtop); + if (gvec) + free(gvec); + if (rlistvec) + free(rlistvec); + if (rvec) + free(rvec); + return retval; + deny: /* Here, cbret must contain a netconf error msg */ + assert(cbuf_len(cbret)); + retval = 0; + goto done; +} + /*! An internal clicon message has arrived from a client. Receive and dispatch. + * @param[in] h Clicon handle * @param[in] s Socket where message arrived. read from this. * @param[in] arg Client entry (from). * @retval 0 OK @@ -824,7 +1070,10 @@ from_client_msg(clicon_handle h, cbuf *cbret = NULL; /* return message */ int pid; int ret; + char *username; + char *nacm_mode; + clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; /* Return netconf message. Should be filled in by the dispatch(sub) functions * as wither rpc-error or by positive response. @@ -844,8 +1093,19 @@ from_client_msg(clicon_handle h, goto reply; } xe = NULL; + username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { name = xml_name(xe); + clicon_debug(1, "%s name:%s", __FUNCTION__, name); +#if 1 /* NACM */ + /* Make NACM access control if enabled as "internal"*/ + nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); + if (nacm_mode && strcmp(nacm_mode,"internal") == 0) + if ((ret = nacm_access(h, name, username, cbret)) < 0) + goto done; + if (!ret) + goto reply; +#endif if (strcmp(name, "get-config") == 0){ if (from_client_get_config(h, xe, cbret) <0) goto done; @@ -947,6 +1207,7 @@ from_client_msg(clicon_handle h, // ok: retval = 0; done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xt) xml_free(xt); if (cbret) @@ -976,6 +1237,7 @@ from_client(int s, clicon_handle h = ce->ce_handle; int eof; + clicon_debug(1, "%s", __FUNCTION__); // assert(s == ce->ce_s); if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0) goto done; diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index ed2e9e64..346cf859 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -306,7 +306,7 @@ startup_mode_none(clicon_handle h) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; retval = 0; done: @@ -328,7 +328,7 @@ startup_mode_init(clicon_handle h) if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; retval = 0; done: @@ -364,7 +364,7 @@ startup_mode_running(clicon_handle h, if (xmldb_copy(h, "running", "candidate") < 0) goto done; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) @@ -437,7 +437,7 @@ startup_mode_startup(clicon_handle h, if (xmldb_create(h, "startup") < 0) /* diff */ return -1; /* Load plugins and call plugin_init() */ - if (plugin_initiate(h) != 0) + if (backend_plugin_initiate(h) != 0) goto done; /* Clear tmp db */ if (db_reset(h, "tmp") < 0) @@ -475,7 +475,8 @@ startup_mode_startup(clicon_handle h, } int -main(int argc, char **argv) +main(int argc, + char **argv) { int retval = -1; char c; @@ -497,14 +498,12 @@ main(int argc, char **argv) int xml_cache; int xml_pretty; char *xml_format; - + /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); /* Initiate CLICON handle */ if ((h = backend_handle_init()) == NULL) return -1; - if (backend_plugin_init(h) != 0) - return -1; foreground = 0; once = 0; zap = 0; diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index ecc4b587..80d0f128 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -64,31 +64,21 @@ #include "backend_plugin.h" #include "backend_commit.h" -/*! Initialize plugin code (not the plugins themselves) - * @param[in] h Clicon handle - * @retval 0 OK - * @retval -1 Error - */ -int -backend_plugin_init(clicon_handle h) -{ - return 0; -} - /*! Load a plugin group. * @param[in] h Clicon handle * @retval 0 OK * @retval -1 Error */ int -plugin_initiate(clicon_handle h) +backend_plugin_initiate(clicon_handle h) { char *dir; /* Load application plugins */ if ((dir = clicon_backend_dir(h)) == NULL) return 0; - return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir); + return clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, + clicon_option_str(h, "CLICON_BACKEND_REGEXP")); } /*! Request plugins to reset system state @@ -124,6 +114,7 @@ clixon_plugin_reset(clicon_handle h, * @param[in] h clicon handle * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in,out] xml XML tree. + * @param[out] cbret Return xml value cligen buffer * @retval -1 Error * @retval 0 OK * @retval 1 Statedata callback failed @@ -139,8 +130,10 @@ clixon_plugin_statedata(clicon_handle h, yang_spec *yspec; cxobj **xvec = NULL; size_t xlen; + cxobj *xc; clixon_plugin *cp = NULL; plgstatedata_t *fn; /* Plugin statedata fn */ + char *reason = NULL; if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); @@ -159,8 +152,23 @@ clixon_plugin_statedata(clicon_handle h, retval = 1; goto done; /* Dont quit here on user callbacks */ } - if (xml_merge(xtop, x, yspec) < 0) + if (xml_merge(xtop, x, yspec, &reason) < 0) goto done; + if (reason){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (netconf_operation_failed(cb, "rpc", reason)< 0) + goto done; + while ((xc = xml_child_i(xtop, 0)) != NULL) + xml_purge(xc); + if (xml_parse_string(cbuf_get(cb), NULL, &xtop) < 0) + goto done; + cbuf_free(cb); + break; + } if (x){ xml_free(x); x = NULL; @@ -187,6 +195,8 @@ clixon_plugin_statedata(clicon_handle h, goto done; retval = 0; done: + if (reason) + free(reason); if (x) xml_free(x); if (xvec) diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index 846c9f5d..3e9f4d1d 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -67,8 +67,7 @@ typedef struct { /* * Prototypes */ -int backend_plugin_init(clicon_handle h); -int plugin_initiate(clicon_handle h); +int backend_plugin_initiate(clicon_handle h); int clixon_plugin_reset(clicon_handle h, char *db); diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 4845b171..f1d9678e 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -243,17 +244,24 @@ main(int argc, char **argv) char *restarg = NULL; /* what remains after options */ int dump_configfile_xml = 0; yang_spec *yspec; + struct passwd *pw; /* Defaults */ + once = 0; + /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); /* Initiate CLICON handle */ if ((h = cli_handle_init()) == NULL) goto done; - - if (cli_plugin_init(h) != 0) + /* Set username to clicon handle. Use in all communication to backend */ + if ((pw = getpwuid(getuid())) == NULL){ + clicon_err(OE_UNIX, errno, "getpwuid"); goto done; - once = 0; + } + if (clicon_username_set(h, pw->pw_name) < 0) + goto done; + cligen_comment_set(cli_cligen(h), '#'); /* Default to handle #! clicon_cli scripts */ /* diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 81ca2e9e..04fc027c 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -346,7 +346,7 @@ cli_syntax_load (clicon_handle h) /* Load cli plugins */ if (plugin_dir && - clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir)< 0) + clixon_plugins_load(h, CLIXON_PLUGIN_INIT, plugin_dir, NULL)< 0) goto done; if (clispec_file){ if (cli_load_syntax(h, clispec_file, NULL) < 0) @@ -606,15 +606,6 @@ clicon_cliread(clicon_handle h) return ret; } -/*! Initialize plugin code (not the plugins themselves) - * @param[in] h Clicon handle - */ -int -cli_plugin_init(clicon_handle h) -{ - return 0; -} - /* * * CLI PLUGIN INTERFACE, PUBLIC SECTION diff --git a/apps/cli/cli_plugin.h b/apps/cli/cli_plugin.h index 298e1675..2df7fb47 100644 --- a/apps/cli/cli_plugin.h +++ b/apps/cli/cli_plugin.h @@ -63,8 +63,6 @@ typedef struct { void *clixon_str2fn(char *name, void *handle, char **error); -int cli_plugin_init(clicon_handle h); - int clicon_eval(clicon_handle h, char *cmd, cg_obj *match_obj, cvec *vr); int clicon_parse(clicon_handle h, char *cmd, char **mode, int *result); diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index a13296b8..8937bfde 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -120,7 +120,8 @@ expand_dbvar(void *h, yang_stmt *ypath; cxobj *xcur; char *xpathcur; - + char *reason = NULL; + if (argv == NULL || cvec_len(argv) != 2){ clicon_err(OE_PLUGIN, 0, "%s: requires arguments: ", __FUNCTION__); @@ -190,8 +191,12 @@ expand_dbvar(void *h, goto done; } xpathcur = ypath->ys_argument; - if (xml_merge(xt, xtop, yspec) < 0) /* Merge xtop into xt */ + if (xml_merge(xt, xtop, yspec, &reason) < 0) /* Merge xtop into xt */ goto done; + if (reason){ + cli_output(stderr, "%s\n", reason); + goto done; + } if ((xcur = xpath_first(xt, xpath)) == NULL){ clicon_err(OE_DB, 0, "xpath %s should return merged content", xpath); goto done; @@ -241,6 +246,8 @@ expand_dbvar(void *h, ok: retval = 0; done: + if (reason) + free(reason); if (api_path) free(api_path); if (xvec) diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index ad900535..20fd8cdf 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include @@ -309,7 +310,8 @@ main(int argc, clicon_handle h; int use_syslog; char *dir; - + struct passwd *pw; + /* Defaults */ use_syslog = 0; @@ -319,6 +321,14 @@ main(int argc, if ((h = clicon_handle_init()) == NULL) return -1; + /* Set username to clicon handle. Use in all communication to backend */ + if ((pw = getpwuid(getuid())) == NULL){ + clicon_err(OE_UNIX, errno, "getpwuid"); + goto done; + } + if (clicon_username_set(h, pw->pw_name) < 0) + goto done; + while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1) switch (c) { case 'h' : /* help */ @@ -376,6 +386,8 @@ main(int argc, argc -= optind; argv += optind; + + /* Parse yang database spec file */ if (yang_spec_main(h) == NULL) goto done; @@ -386,7 +398,7 @@ main(int argc, /* Initialize plugins group */ if ((dir = clicon_netconf_dir(h)) != NULL) - if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir) < 0) + if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) goto done; /* Call start function is all plugins before we go interactive */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 9f07985c..ab5db7b5 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -212,8 +212,10 @@ notfound(FCGX_Request *r) clicon_debug(1, "%s", __FUNCTION__); path = FCGX_GetParam("DOCUMENT_URI", r->envp); FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */ + FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); FCGX_FPrintF(r->out, "

Not Found

\n"); + FCGX_FPrintF(r->out, "Not Found\n"); FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n", path); return 0; @@ -409,8 +411,8 @@ api_return_err(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); if ((cb = cbuf_new()) == NULL) goto done; - if ((xtag = xpath_first(xerr, "error-tag")) == NULL){ - notfound(r); /* bad reply? */ + if ((xtag = xpath_first(xerr, "//error-tag")) == NULL){ + notfound(r); goto ok; } tagstr = xml_body(xtag); diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 89d52114..80e2e364 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -92,6 +92,9 @@ * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] dvec Stream input daat + * @param[in] pretty Set to 1 for pretty-printed xml/json output + * @param[in] use_xml Set to 0 for JSON and 1 for XML + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data */ static int api_data(clicon_handle h, @@ -100,28 +103,17 @@ api_data(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; char *request_method; - int pretty; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ - char *media_accept; - int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s", __FUNCTION__); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; - if (strcmp(request_method, "OPTIONS")==0) retval = api_data_options(h, r); else if (strcmp(request_method, "HEAD")==0) @@ -150,6 +142,7 @@ api_data(clicon_handle h, * @param[in] pi Offset, where to start pcvec * @param[in] qvec Vector of query string (QUERY_STRING) * @param[in] data Stream input data + * @param[in] parse_xml Set to 0 for JSON and 1 for XML for input data */ static int api_operations(clicon_handle h, @@ -158,28 +151,17 @@ api_operations(clicon_handle h, cvec *pcvec, int pi, cvec *qvec, - char *data) + char *data, + int pretty, + int use_xml, + int parse_xml) { int retval = -1; char *request_method; - int pretty; - char *media_content_type; - int parse_xml = 0; /* By default expect and parse JSON */ - char *media_accept; - int use_xml = 0; /* By default use JSON */ clicon_debug(1, "%s", __FUNCTION__); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); clicon_debug(1, "%s method:%s", __FUNCTION__, request_method); - pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); - media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); - if (strcmp(media_accept, "application/yang-data+xml")==0) - use_xml++; - media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); - if (media_content_type && - strcmp(media_content_type, "application/yang-data+xml")==0) - parse_xml++; - if (strcmp(request_method, "GET")==0) retval = api_operations_get(h, r, path, pcvec, pi, qvec, data, pretty, use_xml); else if (strcmp(request_method, "POST")==0) @@ -293,7 +275,6 @@ api_yang_library_version(clicon_handle h, if (xml_rootchild(xt, 0, &xt) < 0) goto done; if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (use_xml){ @@ -335,16 +316,33 @@ api_restconf(clicon_handle h, cbuf *cb = NULL; char *data; int authenticated = 0; + char *media_accept; + char *media_content_type; + int pretty; + int parse_xml = 0; /* By default expect and parse JSON */ + int use_xml = 0; /* By default use JSON */ + cbuf *cbret = NULL; + cxobj *xret = NULL; + cxobj *xerr; clicon_debug(1, "%s", __FUNCTION__); path = FCGX_GetParam("REQUEST_URI", r->envp); query = FCGX_GetParam("QUERY_STRING", r->envp); + pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); + /* get xml/json in put and output */ + media_accept = FCGX_GetParam("HTTP_ACCEPT", r->envp); + if (media_accept && strcmp(media_accept, "application/yang-data+xml")==0) + use_xml++; + media_content_type = FCGX_GetParam("HTTP_CONTENT_TYPE", r->envp); + if (media_content_type && + strcmp(media_content_type, "application/yang-data+xml")==0) + parse_xml++; if ((pvec = clicon_strsep(path, "/", &pn)) == NULL) goto done; /* Sanity check of path. Should be /restconf/ */ if (pn < 2){ - retval = notfound(r); - goto done; + notfound(r); + goto ok; } if (strlen(pvec[0]) != 0){ retval = notfound(r); @@ -390,7 +388,13 @@ api_restconf(clicon_handle h, clicon_username_set(h, "none"); } else{ - unauthorized(r); + if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) + goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } goto ok; } clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h)); @@ -399,11 +403,13 @@ api_restconf(clicon_handle h, goto done; } else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */ - if (api_data(h, r, path, pcvec, 2, qvec, data) < 0) + if (api_data(h, r, path, pcvec, 2, qvec, data, + pretty, use_xml, parse_xml) < 0) goto done; } else if (strcmp(method, "operations") == 0){ /* rpc */ - if (api_operations(h, r, path, pcvec, 2, qvec, data) < 0) + if (api_operations(h, r, path, pcvec, 2, qvec, data, + pretty, use_xml, parse_xml) < 0) goto done; } else if (strcmp(method, "test") == 0) @@ -424,6 +430,10 @@ api_restconf(clicon_handle h, cvec_free(pcvec); if (cb) cbuf_free(cb); + if (cbret) + cbuf_free(cbret); + if (xret) + xml_free(xret); return retval; } @@ -557,7 +567,7 @@ main(int argc, /* Initialize plugins group */ if ((dir = clicon_restconf_dir(h)) != NULL) - if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir) < 0) + if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0) return -1; /* Parse yang database spec file */ @@ -598,7 +608,6 @@ main(int argc, clicon_debug(1, "top-level %s not found", path); notfound(r); } - } else clicon_debug(1, "NULL URI"); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 22b3c322..ae233df3 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -185,7 +185,7 @@ api_data_get2(clicon_handle h, cbuf *cbx = NULL; yang_spec *yspec; cxobj *xret = NULL; - cxobj *xerr; + cxobj *xerr = NULL; cxobj **xvec = NULL; size_t xlen; int i; @@ -199,13 +199,19 @@ api_data_get2(clicon_handle h, clicon_debug(1, "%s pi:%d", __FUNCTION__, pi); /* We know "data" is element pi-1 */ if (api_path2xpath_cvv(yspec, pcvec, pi, cbpath) < 0){ - notfound(r); + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } path = cbuf_get(cbpath); clicon_debug(1, "%s path:%s", __FUNCTION__, path); if (clicon_rpc_get(h, path, &xret) < 0){ - notfound(r); + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* We get return via netconf which is complete tree from root @@ -394,10 +400,9 @@ api_data_post(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; - cxobj *xu; cxobj *xret = NULL; cxobj *xretcom = NULL; - cxobj *xerr; + cxobj *xerr = NULL; char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -414,16 +419,6 @@ api_data_post(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ - if ((username = clicon_username_get(h)) != NULL){ - if ((xu = xml_new("username", xtop, NULL)) == NULL) - goto done; - xml_type_set(xu, CX_ATTR); - if (xml_value_set(xu, username) < 0) - goto done; - } - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ @@ -457,7 +452,11 @@ api_data_post(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; @@ -471,7 +470,10 @@ api_data_post(clicon_handle h, goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) @@ -600,11 +602,10 @@ api_data_put(clicon_handle h, yang_node *y = NULL; yang_spec *yspec; cxobj *xa; - cxobj *xu; char *api_path; cxobj *xret = NULL; cxobj *xretcom = NULL; - cxobj *xerr; + cxobj *xerr = NULL; char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -621,15 +622,7 @@ api_data_put(clicon_handle h, goto done; /* Translate api_path to xtop/xbot */ xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ - if ((username = clicon_username_get(h)) != NULL){ - if ((xu = xml_new("username", xtop, NULL)) == NULL) - goto done; - xml_type_set(xu, CX_ATTR); - if (xml_value_set(xu, username) < 0) - goto done; - } + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; /* Parse input data as json or xml into xml */ @@ -688,7 +681,11 @@ api_data_put(clicon_handle h, /* Create text buffer for transfer to backend */ if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; @@ -701,7 +698,10 @@ api_data_put(clicon_handle h, goto done; goto ok; } - if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) @@ -779,14 +779,13 @@ api_data_delete(clicon_handle h, cxobj *xtop = NULL; /* xpath root */ cxobj *xbot = NULL; cxobj *xa; - cxobj *xu; cbuf *cbx = NULL; yang_node *y = NULL; yang_spec *yspec; enum operation_type op = OP_DELETE; cxobj *xret = NULL; cxobj *xretcom = NULL; - cxobj *xerr; + cxobj *xerr = NULL; char *username; clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path); @@ -800,15 +799,7 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - /* For internal XML protocol: add username attribute for backend access control - */ - if ((username = clicon_username_get(h)) != NULL){ - if ((xu = xml_new("username", xtop, NULL)) == NULL) - goto done; - xml_type_set(xu, CX_ATTR); - if (xml_value_set(xu, username) < 0) - goto done; - } + if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) @@ -818,7 +809,11 @@ api_data_delete(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; - cprintf(cbx, ""); + /* For internal XML protocol: add username attribute for access control + */ + username = clicon_username_get(h); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); cprintf(cbx, "none"); if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; @@ -831,7 +826,10 @@ api_data_delete(clicon_handle h, goto ok; } /* Assume this is validation failed since commit includes validate */ - if (clicon_rpc_netconf(h, "", &xretcom, NULL) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) @@ -984,7 +982,7 @@ api_operations_post(clicon_handle h, yang_stmt *youtput; cxobj *xdata = NULL; cxobj *xret = NULL; - cxobj *xerr; + cxobj *xerr = NULL; cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ cxobj *xe; @@ -1011,7 +1009,10 @@ api_operations_post(clicon_handle h, if (yang_abs_schema_nodeid(yspec, oppath, &yrpc) < 0) goto done; if (yrpc == NULL){ - retval = notfound(r); + if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; goto ok; } /* Create an xml message: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index bd6206a0..9bc96f46 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -770,7 +770,7 @@ text_modify_top(cxobj *x0, if (xml_operation(opstr, &op) < 0) goto done; /* Special case if x1 is empty, top-level only */ - if (!xml_child_nr(x1)){ + if (xml_child_nr(x1) == 0){ if (xml_child_nr(x0)) /* base tree not empty */ switch(op){ case OP_DELETE: @@ -797,7 +797,7 @@ text_modify_top(cxobj *x0, /* Special case top-level replace */ if (op == OP_REPLACE || op == OP_DELETE){ x0c = NULL; - while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) + while ((x0c = xml_child_i(x0, 0)) != 0) xml_purge(x0c); } /* Loop through children of the modification tree */ @@ -806,7 +806,7 @@ text_modify_top(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); + clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", x1, x1cname); goto done; } /* See if there is a corresponding node in the base tree */ diff --git a/example/Makefile.in b/example/Makefile.in index acbeb4ce..418e7b07 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -45,7 +45,7 @@ CFLAGS = @CFLAGS@ -rdynamic -fPIC INCLUDES = -I$(includedir) @INCLUDES@ BE_PLUGIN = $(APPNAME)_backend.so -BE2_PLUGIN = $(APPNAME)_backend_secondary.so +BE2_PLUGIN = $(APPNAME)_backend_nacm.so CLI_PLUGIN = $(APPNAME)_cli.so NETCONF_PLUGIN = $(APPNAME)_netconf.so RESTCONF_PLUGIN = $(APPNAME)_restconf.so @@ -75,8 +75,8 @@ BE_OBJ = $(BE_SRC:%.c=%.o) $(BE_PLUGIN): $(BE_OBJ) $(CC) -Wall -shared -o $@ -lc $< -# Secondary backend plugin -BE2_SRC = $(APPNAME)_backend_secondary.c +# Secondary NACM backend plugin +BE2_SRC = $(APPNAME)_backend_nacm.c BE2_OBJ = $(BE2_SRC:%.c=%.o) $(BE2_PLUGIN): $(BE2_OBJ) $(CC) -Wall -shared -o $@ -lc $< diff --git a/example/example.yang b/example/example.yang index 42d8236f..08799eae 100644 --- a/example/example.yang +++ b/example/example.yang @@ -6,23 +6,30 @@ module example { import ietf-routing { prefix rt; } + import ietf-netconf-acm { + prefix nacm; + } description "Example code that includes ietf-ip and ietf-routing"; - leaf basic_auth{ - description "Basic user / password authentication as in HTTP basic auth"; - type boolean; - default false; - } - list auth { - description "user / password entries. Valid if basic_auth=true"; - key user; - leaf user{ - description "User name"; - type string; + container authentication { + description "Example code for enabling www basic auth and some example + users"; + leaf basic_auth{ + description "Basic user / password authentication as in HTTP basic auth"; + type boolean; + default false; } - leaf password{ - description "Password"; - type string; + list auth { + description "user / password entries. Valid if basic_auth=true"; + key user; + leaf user{ + description "User name"; + type string; + } + leaf password{ + description "Password"; + type string; + } } } rpc client-rpc { diff --git a/example/example_backend.c b/example/example_backend.c index 501fa7bf..28f5611e 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -251,22 +251,18 @@ plugin_start(clicon_handle h, clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { - "example", /* name */ + "example", /* name */ /*--- Common fields. ---*/ clixon_plugin_init, /* init */ plugin_start, /* start */ NULL, /* exit */ - NULL, /* auth */ - NULL, /* cli prompt */ - NULL, /* cli suspend */ - NULL, /* cli interrupt */ - plugin_reset, /* reset */ - plugin_statedata, /* statedata */ - NULL, /* trans begin */ - transaction_validate,/* trans validate */ - NULL, /* trans complete */ - transaction_commit, /* trans commit */ - NULL, /* trans end */ - NULL /* trans abort */ + .ca_reset=plugin_reset,/* reset */ /*--- Backend plugin only ---*/ + .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 */ }; /*! Backend plugin initialization diff --git a/example/example_backend_secondary.c b/example/example_backend_nacm.c similarity index 67% rename from example/example_backend_secondary.c rename to example/example_backend_nacm.c index 3c053540..cdc7346a 100644 --- a/example/example_backend_secondary.c +++ b/example/example_backend_nacm.c @@ -55,16 +55,41 @@ #include -int -transaction_commit_2(clicon_handle h, - transaction_data td) +/*! Called to get NACM state data + * @param[in] h Clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] xtop XML tree, on entry. + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get + * @note this example code returns a static statedata used in testing. + * Real code would poll state + */ +int +nacm_statedata(clicon_handle h, + char *xpath, + cxobj *xstate) { - clicon_debug(1, "%s", __FUNCTION__); - return 0; + int retval = -1; + cxobj **xvec = NULL; + + /* Example of (static) statedata, real code would poll state */ + if (xml_parse_string("" + "0" + "0" + "0" + "", NULL, &xstate) < 0) + goto done; + retval = 0; + done: + if (xvec) + free(xvec); + return retval; } + int -plugin_start_2(clicon_handle h, +plugin_start(clicon_handle h, int argc, char **argv) { @@ -74,19 +99,12 @@ plugin_start_2(clicon_handle h, clixon_plugin_api *clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { - "secondary", /* name */ + "nacm", /* name */ /*--- Common fields. ---*/ clixon_plugin_init, /* init */ - plugin_start_2, /* start */ + plugin_start, /* start */ NULL, /* exit */ - NULL, /* auth */ - NULL, /* reset */ - NULL, /* statedata */ - NULL, /* trans begin */ - NULL, /* trans validate */ - NULL, /* trans complete */ - transaction_commit_2,/* trans commit */ - NULL, /* trans end */ - NULL /* trans abort */ + .ca_reset=NULL, /* reset */ /*--- Backend plugin only ---*/ + .ca_statedata=nacm_statedata, /* statedata */ }; /*! Backend plugin initialization diff --git a/example/example_cli.c b/example/example_cli.c index ddaa640b..b8a91fa3 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -117,10 +117,9 @@ static clixon_plugin_api api = { clixon_plugin_init, /* init */ NULL, /* start */ NULL, /* exit */ - NULL, /* auth */ - NULL, /* cli_prompthook_t */ - NULL, /* cligen_susp_cb_t */ - NULL, /* cligen_interrupt_cb_t */ + .ca_prompt=NULL, /* cli_prompthook_t */ + .ca_suspend=NULL, /* cligen_susp_cb_t */ + .ca_interrupt=NULL, /* cligen_interrupt_cb_t */ }; /*! CLI plugin initialization diff --git a/example/example_netconf.c b/example/example_netconf.c index dc8f2543..9993e851 100644 --- a/example/example_netconf.c +++ b/example/example_netconf.c @@ -81,8 +81,7 @@ static struct clixon_plugin_api api = { "example", /* name */ clixon_plugin_init, /* init */ plugin_start, /* start */ - plugin_exit, /* exit */ - NULL /* auth */ + plugin_exit /* exit */ }; /*! Netconf plugin initialization diff --git a/example/example_restconf.c b/example/example_restconf.c index 2f01fe98..f16199e1 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -180,7 +180,6 @@ b64_decode(const char *src, return (tarindex); } - /*! Process a rest request that requires (cookie) "authentication" * Note, this is loaded as dlsym fixed symbol in plugin * @param[in] h Clixon handle @@ -188,7 +187,7 @@ b64_decode(const char *src, * @retval -1 Fatal error * @retval 0 Unauth * @retval 1 Auth - * For grideye, return "u" entry name if it has a valid "user" entry. + * */ int plugin_credentials(clicon_handle h, @@ -206,12 +205,17 @@ plugin_credentials(clicon_handle h, size_t authlen; cbuf *cb = NULL; int ret; - + + /* XXX This is a kludge to reset the user not remaining from previous */ + if (clicon_username_set(h, "admin") < 0) + goto done; clicon_debug(1, "%s", __FUNCTION__); /* Check if basic_auth set, if not return OK */ - if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) + if (clicon_rpc_get_config(h, "running", "authentication", &xt) < 0) goto done; - if ((x = xpath_first(xt, "basic_auth")) == NULL) + if (clicon_username_set(h, "none") < 0) + goto done; + if ((x = xpath_first(xt, "authentication/basic_auth")) == NULL) goto ok; if ((xbody = xml_body(x)) == NULL) goto ok; @@ -219,8 +223,8 @@ plugin_credentials(clicon_handle h, goto ok; /* At this point in the code we must use HTTP basic authentication */ if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL) - goto fail; - if (strlen(auth) < strlen("Basic ")) + goto fail; + if (strlen(auth) < strlen("Basic ")) goto fail; if (strncmp("Basic ", auth, strlen("Basic "))) goto fail; @@ -239,15 +243,18 @@ plugin_credentials(clicon_handle h, *passwd = '\0'; passwd++; clicon_debug(1, "%s user:%s passwd:%s", __FUNCTION__, user, passwd); + /* Here get auth sub-tree whjere all the users are */ if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "auth[user=%s]", user); + cprintf(cb, "authentication/auth[user=%s]", user); if ((x = xpath_first(xt, cbuf_get(cb))) == NULL) goto fail; + passwd2 = xml_find_body(x, "password"); if (strcmp(passwd, passwd2)) goto fail; retval = 1; + clicon_debug(1, "%s user:%s", __FUNCTION__, user); if (clicon_username_set(h, user) < 0) goto done; ok: /* authenticated */ @@ -281,7 +288,6 @@ restconf_client_rpc(clicon_handle h, return 0; } - clixon_plugin_api * clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { @@ -289,7 +295,7 @@ static clixon_plugin_api api = { clixon_plugin_init, /* init */ NULL, /* start */ NULL, /* exit */ - plugin_credentials /* auth */ + .ca_auth=plugin_credentials /* auth */ }; /*! Restconf plugin initialization diff --git a/lib/clixon/clixon_netconf_lib.h b/lib/clixon/clixon_netconf_lib.h index 847de984..8ba54c1c 100644 --- a/lib/clixon/clixon_netconf_lib.h +++ b/lib/clixon/clixon_netconf_lib.h @@ -51,6 +51,7 @@ int netconf_bad_element(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_element(cbuf *cb, char *type, char *info, char *message); int netconf_unknown_namespace(cbuf *cb, char *type, char *info, char *message); int netconf_access_denied(cbuf *cb, char *type, char *message); +int netconf_access_denied_xml(cxobj **xret, char *type, char *message); int netconf_lock_denied(cbuf *cb, char *info, char *message); int netconf_resource_denied(cbuf *cb, char *type, char *message); int netconf_rollback_failed(cbuf *cb, char *type, char *message); @@ -58,6 +59,7 @@ int netconf_data_exists(cbuf *cb, char *message); int netconf_data_missing(cbuf *cb, char *message); 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); #endif /* _CLIXON_NETCONF_LIB_H */ diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index 536b0c86..ece605c7 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -122,23 +122,44 @@ struct clixon_plugin_api{ plginit2_t *ca_init; /* Clixon plugin Init (implicit) */ plgstart_t *ca_start; /* Plugin start */ plgexit_t *ca_exit; /* Plugin exit */ - plgauth_t *ca_auth; /* Auth credentials */ + union { + struct { + cli_prompthook_t *ci_prompt; /* Prompt hook */ + cligen_susp_cb_t *ci_suspend; /* Ctrl-Z hook, see cligen getline */ + cligen_interrupt_cb_t *ci_interrupt; /* Ctrl-C, see cligen getline */ + } cau_cli; + struct { + plgauth_t *cr_auth; /* Auth credentials */ + } cau_restconf; + struct { + } cau_netconf; + struct { + plgreset_t *cb_reset; /* Reset system status (backend only) */ + plgstatedata_t *cb_statedata; /* Get state data from plugin (backend only) */ + trans_cb_t *cb_trans_begin; /* Transaction start */ + trans_cb_t *cb_trans_validate; /* Transaction validation */ + trans_cb_t *cb_trans_complete; /* Transaction validation complete */ + trans_cb_t *cb_trans_commit; /* Transaction commit */ + trans_cb_t *cb_trans_end; /* Transaction completed */ + trans_cb_t *cb_trans_abort; /* Transaction aborted */ + } cau_backend; - /*--- CLI plugin-only ---*/ - cli_prompthook_t *ca_prompt; /* Prompt hook */ - cligen_susp_cb_t *ca_suspend; /* Ctrl-Z hook, see cligen getline */ - cligen_interrupt_cb_t *ca_interrupt; /* Ctrl-C, see cligen getline */ - - /*--- Backend plugin only ---*/ - plgreset_t *ca_reset; /* Reset system status (backend only) */ - plgstatedata_t *ca_statedata; /* Get state data from plugin (backend only) */ - trans_cb_t *ca_trans_begin; /* Transaction start */ - trans_cb_t *ca_trans_validate; /* Transaction validation */ - trans_cb_t *ca_trans_complete; /* Transaction validation complete */ - trans_cb_t *ca_trans_commit; /* Transaction commit */ - trans_cb_t *ca_trans_end; /* Transaction completed */ - trans_cb_t *ca_trans_abort; /* Transaction aborted */ + } u; }; +/* Access fields */ +#define ca_prompt u.cau_cli.ci_prompt +#define ca_suspend u.cau_cli.ci_suspend +#define ca_interrupt u.cau_cli.ci_interrupt +#define ca_auth u.cau_restconf.cr_auth +#define ca_reset u.cau_backend.cb_reset +#define ca_statedata u.cau_backend.cb_statedata +#define ca_trans_begin u.cau_backend.cb_trans_begin +#define ca_trans_validate u.cau_backend.cb_trans_validate +#define ca_trans_complete u.cau_backend.cb_trans_complete +#define ca_trans_commit u.cau_backend.cb_trans_commit +#define ca_trans_end u.cau_backend.cb_trans_end +#define ca_trans_abort u.cau_backend.cb_trans_abort + typedef struct clixon_plugin_api clixon_plugin_api; /* Internal plugin structure with dlopen() handle and plugin_api @@ -167,7 +188,7 @@ clixon_plugin *clixon_plugin_each_revert(clicon_handle h, clixon_plugin *cpprev, clixon_plugin *clixon_plugin_find(clicon_handle h, char *name); -int clixon_plugins_load(clicon_handle h, char *function, char *dir); +int clixon_plugins_load(clicon_handle h, char *function, char *dir, char *regexp); int clixon_plugin_start(clicon_handle h, int argc, char **argv); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 71cb0e46..6a5a32fe 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -65,7 +65,7 @@ int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, yang_class nodeclass, cxobj **xpathp, yang_node **ypathp); -int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); +int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec, char **reason); int yang_enum_int_value(cxobj *node, int32_t *val); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/src/clixon_file.c b/lib/src/clixon_file.c index 2377352d..12df1b83 100644 --- a/lib/src/clixon_file.c +++ b/lib/src/clixon_file.c @@ -125,16 +125,16 @@ clicon_file_dirent(const char *dir, clicon_err(OE_DB, 0, "regcomp: %s", errbuf); return -1; } - if ((dirp = opendir (dir)) == NULL) { + if ((dirp = opendir(dir)) == NULL) { if (errno == ENOENT) /* Dir does not exist -> return 0 matches */ retval = 0; else clicon_err(OE_UNIX, errno, "opendir(%s)", dir); goto quit; } - for (res = readdir_r (dirp, &dent, &dresp); + for (res = readdir_r(dirp, &dent, &dresp); dresp; - res = readdir_r (dirp, &dent, &dresp)) { + res = readdir_r(dirp, &dent, &dresp)) { if (res != 0) { clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno)); goto quit; @@ -161,7 +161,7 @@ clicon_file_dirent(const char *dir, goto quit; } new = tmp; - memcpy (&new[nent], &dent, sizeof(dent)); + memcpy(&new[nent], &dent, sizeof(dent)); nent++; } /* while */ diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index 09cf1e41..a2a70fc0 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -57,6 +57,7 @@ #include "clixon_err.h" #include "clixon_handle.h" #include "clixon_yang.h" +#include "clixon_log.h" #include "clixon_xml.h" #include "clixon_netconf_lib.h" @@ -438,6 +439,38 @@ netconf_access_denied(cbuf *cb, goto done; } +/*! Create Netconf access-denied error XML tree according to RFC 6241 App A + * + * An expected element is missing. + * @param[out] xret Error XML tree + * @param[in] type Error type: "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_access_denied_xml(cxobj **xret, + char *type, + char *message) +{ + int retval =-1; + cbuf *cbret = NULL; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (netconf_access_denied(cbret, type, message) < 0) + goto done; + if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + goto done; + if (xml_rootchild(*xret, 0, xret) < 0) + goto done; + retval = 0; + done: + if (cbret) + cbuf_free(cbret); + return retval; +} + /*! Create Netconf lock-denied error XML tree according to RFC 6241 App A * * Access to the requested lock is denied because the lock is currently held @@ -655,7 +688,7 @@ netconf_operation_failed(cbuf *cb, goto err; if (message && cprintf(cb, "%s", message) < 0) goto err; - if (cprintf(cb, "
") <0) + if (cprintf(cb, "") < 0) goto err; retval = 0; done: @@ -665,6 +698,39 @@ netconf_operation_failed(cbuf *cb, goto done; } +/*! Create Netconf operation-failed error XML tree according to RFC 6241 App A + * + * Request could not be completed because the requested operation failed for + * some reason not covered by any other error condition. + * @param[out] xret Error XML tree + * @param[in] type Error type: "rpc", "application" or "protocol" + * @param[in] message Error message + */ +int +netconf_operation_failed_xml(cxobj **xret, + char *type, + char *message) +{ + int retval =-1; + cbuf *cbret = NULL; + + if ((cbret = cbuf_new()) == NULL){ + clicon_err(OE_XML, errno, "cbuf_new"); + goto done; + } + if (netconf_operation_failed(cbret, type, message) < 0) + goto done; + if (xml_parse_string(cbuf_get(cbret), NULL, xret) < 0) + goto done; + if (xml_rootchild(*xret, 0, xret) < 0) + goto done; + retval = 0; + done: + if (cbret) + cbuf_free(cbret); + return retval; +} + /*! 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. diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 3675b37b..4b6112c2 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -242,13 +242,15 @@ plugin_load_one(clicon_handle h, * @param[in] h Clicon handle * @param[in] function Which function symbol to load and call (eg CLIXON_PLUGIN_INIT) * @param[in] dir Directory. .so files in this dir will be loaded. + * @param[in] regexp Regexp for matching files in plugin directory. Default *.so. * @retval 0 OK * @retval -1 Error */ int clixon_plugins_load(clicon_handle h, char *function, - char *dir) + char *dir, + char *regexp) { int retval = -1; int ndp; @@ -259,7 +261,8 @@ clixon_plugins_load(clicon_handle h, clicon_debug(1, "%s", __FUNCTION__); /* Get plugin objects names from plugin directory */ - if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0) + if((ndp = clicon_file_dirent(dir, &dp, + regexp?regexp:"(.so)$", S_IFREG))<0) goto done; /* Load all plugins */ for (i = 0; i < ndp; i++) { diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 7fd7e08f..7c1bec04 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -91,6 +91,7 @@ clicon_rpc_msg(clicon_handle h, cxobj *xret = NULL; yang_spec *yspec; + clicon_debug(1, "%s request:%s", __FUNCTION__, msg->op_body); if ((sock = clicon_sock(h)) == NULL){ clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); goto done; @@ -327,10 +328,14 @@ clicon_rpc_edit_config(clicon_handle h, cbuf *cb = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "<%s/>", db); + cprintf(cb, "<%s/>", db); cprintf(cb, "%s", xml_operation2str(op)); if (xmlstr) @@ -377,8 +382,12 @@ clicon_rpc_copy_config(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/><%s/>", db1, db2)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/><%s/>", + username?username:"", + db1, db2)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -413,8 +422,11 @@ clicon_rpc_delete_config(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", + username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -445,8 +457,11 @@ clicon_rpc_lock(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", + username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -477,8 +492,10 @@ clicon_rpc_unlock(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -574,8 +591,11 @@ clicon_rpc_close_session(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", + username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -606,8 +626,11 @@ clicon_rpc_kill_session(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("%d", session_id)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("%d", + username?username:"", session_id)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -638,8 +661,10 @@ clicon_rpc_validate(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("<%s/>", db)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("<%s/>", username?username:"", db)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -668,8 +693,10 @@ clicon_rpc_commit(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -698,8 +725,10 @@ clicon_rpc_discard_changes(clicon_handle h) struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("")) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("", username?username:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; @@ -736,11 +765,14 @@ clicon_rpc_create_subscription(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("" + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("" "%s" "%s" "", + username?username:"", stream?stream:"", filter?filter:"")) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, s0) < 0) @@ -772,8 +804,10 @@ clicon_rpc_debug(clicon_handle h, struct clicon_msg *msg = NULL; cxobj *xret = NULL; cxobj *xerr; + char *username; - if ((msg = clicon_msg_encode("%d", level)) == NULL) + username = clicon_username_get(h); + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index bf64d04d..701615e8 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -317,8 +317,9 @@ xml_yang_validate_add(cxobj *xt, yang_stmt *ys; char *body; - /* if not given by argument (overide) use default link */ - if ((ys = xml_spec(xt)) != NULL){ + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ switch (ys->ys_keyword){ case Y_LIST: /* fall thru */ @@ -327,6 +328,8 @@ xml_yang_validate_add(cxobj *xt, yc = ys->ys_stmt[i]; if (yc->ys_keyword != Y_LEAF) continue; + if (yang_config(yc)==0) + continue; if (yang_mandatory(yc) && xml_find(xt, yc->ys_argument)==NULL){ clicon_err(OE_CFG, 0,"Missing mandatory variable: %s", yc->ys_argument); @@ -386,8 +389,10 @@ xml_yang_validate_all(cxobj *xt, yang_stmt *ys; yang_stmt *ytype; - /* if not given by argument (overide) use default link */ - if ((ys = xml_spec(xt)) != NULL){ + /* if not given by argument (overide) use default link + and !Node has a config sub-statement and it is false */ + if ((ys = xml_spec(xt)) != NULL && + yang_config(ys) != 0){ switch (ys->ys_keyword){ case Y_LEAF: /* fall thru */ @@ -1644,6 +1649,9 @@ api_path2xml(char *api_path, * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base + * @param[out] reason If retval=0 a malloced string + * @retval 0 OK. If reason is set, Yang error + * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ @@ -1651,7 +1659,8 @@ static int xml_merge1(cxobj *x0, yang_node *y0, cxobj *x0p, - cxobj *x1) + cxobj *x1, + char **reason) { int retval = -1; char *x1name; @@ -1699,24 +1708,35 @@ xml_merge1(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_datanode(y0, x1cname)) == NULL){ - clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); - goto done; + if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; } /* See if there is a corresponding node in the base tree */ x0c = NULL; if (yc && match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; + if (*reason != NULL) + goto ok; } } /* else Y_CONTAINER */ - // ok: + ok: retval = 0; done: return retval; } /*! Merge XML trees x1 into x0 according to yang spec yspec + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] x1 xml tree which modifies base + * @param[in] yspec Yang spec + * @param[out] reason If retval=0 a malloced string. Needs to be freed by caller + * @retval 0 OK. If reason is set, Yang error + * @retval -1 Error * @note both x0 and x1 need to be top-level trees * @see text_modify_top as more generic variant (in datastore text) * @note returns -1 if YANG do not match, you may want to have a softer error @@ -1724,7 +1744,8 @@ xml_merge1(cxobj *x0, int xml_merge(cxobj *x0, cxobj *x1, - yang_spec *yspec) + yang_spec *yspec, + char **reason) { int retval = -1; char *x1cname; /* child name */ @@ -1738,16 +1759,21 @@ xml_merge(cxobj *x0, x1cname = xml_name(x1c); /* Get yang spec of the child */ if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; + if (reason && (*reason = strdup("XML node has no corresponding yang specification (Invalid XML or wrong Yang spec?")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) goto done; - if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c, reason) < 0) goto done; + if (*reason != NULL) + break; } - retval = 0; + retval = 0; /* OK */ done: return retval; } diff --git a/test/test_auth.sh b/test/test_auth.sh index 6ccf1d3c..68339ab8 100755 --- a/test/test_auth.sh +++ b/test/test_auth.sh @@ -1,5 +1,7 @@ #!/bin/bash -# Authentication and authorization +# Authentication and authorization and IETF NACM +# See RFC 8321 A.2 +# But replaced ietf-netconf-monitoring with * APPNAME=example # include err() and new() functions and creates $dir @@ -24,18 +26,25 @@ cat < $cfg /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so false + internal EOF cat < $fyang module $APPNAME{ prefix ex; - leaf basic_auth{ + import ietf-netconf-acm { + prefix nacm; + } + container authentication { + description "Example code for enabling www basic auth and some example + users"; + leaf basic_auth{ description "Basic user / password authentication as in HTTP basic auth"; type boolean; default false; - } - list auth { + } + list auth { description "user / password entries. Valid if basic_auth=true"; key user; leaf user{ @@ -46,10 +55,106 @@ module $APPNAME{ description "Password"; type string; } + } } + leaf x{ + type int32; + description "something to edit"; + } } EOF +RULES=$(cat < + true + + adm1bar + + + wilmabar + + + guestbar + + + + false + deny + deny + deny + + + admin + admin + adm1 + olof + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + + + guest-acl + guest + + deny-ncm + * + * + deny + + Do not allow guests any access to any information. + + + + + limited-acl + limited + + permit-get + get + * + exec + permit + + Allow get + + + + permit-get-config + get-config + * + exec + permit + + Allow get-config + + + + + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + 0 +EOF +) + # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg -y $fyang @@ -66,29 +171,60 @@ fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf - +sleep 1 new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang sleep 1 +new "restconf DELETE whole datastore" +expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" + new2 "auth get" -expecteq "$(curl -sS -X GET http://localhost/restconf/data)" '{"data": null} +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} ' new "auth set authentication config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "truefoobar]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "$RULES]]>]]>" "^]]>]]>$" +new "commit it" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" -new2 "auth get (access denied)" -expecteq "$(curl -sS -X GET http://localhost/restconf/data)" "access-denied -The requested URL /restconf/data was unauthorized." +new2 "auth get (no user: access denied)" +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (wrong passwd: access denied)" +expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u foo:bar -sS -X GET http://localhost/restconf/data)" '{"data": {"basic_auth": true,"auth": [{"user": "foo","password": "bar"}]}} +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' +#----------------Enable NACM + +new "enable nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +new2 "admin get nacm" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "limited get nacm" +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "guest get nacm" +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' + +new "admin edit nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" + +new2 "limited edit nacm" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new2 "guest edit nacm" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' + new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 3dba5353..7901ea9c 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -15,6 +15,7 @@ cat < $cfg $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend + example_backend.so$ /usr/local/lib/$APPNAME/restconf false /usr/local/lib/$APPNAME/cli diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index c5c80e4b..58ff73f8 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -82,6 +82,24 @@ module clixon-config { } } } + typedef nacm_mode{ + description + "Mode of RFC8341 Network Configuration Access Control Model. + It is unclear from the RFC whether NACM rules are internal + in a configuration (ie embedded in regular config) or external/OOB + in s separate, specific NACM-config"; + type enumeration{ + enum disabled{ + description "NACM is disabled"; + } + enum internal{ + description "NACM is enabled and available in the regular config"; + } + enum external{ + description "NACM is enabled and available in a separate config"; + } + } + } container config { leaf CLICON_CONFIGFILE{ type string; @@ -113,6 +131,12 @@ module clixon-config { "Location of backend .so plugins. Load all .so plugins in this dir as backend plugins"; } + leaf CLICON_BACKEND_REGEXP { + type string; + description + "Regexp of matching backend plugins in CLICON_BACKEND_DIR"; + default "(.so)$"; + } leaf CLICON_NETCONF_DIR { type string; description "Location of netconf (frontend) .so plugins"; @@ -298,5 +322,9 @@ module clixon-config { description "If set, modifications in validation and commit callbacks are written back into the datastore"; } + leaf CLICON_NACM_MODE { + type nacm_mode; + default disabled; + description "RFC8341 network access configuration control model"; } } } From 67c0abead789c8479a59e5428b3279cbdc7d7da9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 21 Apr 2018 16:32:46 +0200 Subject: [PATCH 39/50] Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs --- CHANGELOG.md | 1 + apps/backend/backend_client.c | 6 +- apps/cli/cli_show.c | 8 +- datastore/keyvalue/clixon_keyvalue.c | 10 +- lib/clixon/clixon_string.h | 5 +- lib/src/clixon_netconf_lib.c | 248 ++++++++++++++++++++------- lib/src/clixon_string.c | 125 ++++++++++++-- lib/src/clixon_xml.c | 30 +++- lib/src/clixon_xml_map.c | 25 +-- lib/src/clixon_xml_parse.h | 1 + lib/src/clixon_xml_parse.l | 48 +++--- lib/src/clixon_xml_parse.y | 16 +- test/test_cli.sh | 5 + test/test_netconf.sh | 10 +- test/test_type.sh | 3 +- 15 files changed, 405 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36450023..bbf2c9a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs +* Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs * Fixed three-key list entry problem (reported by jdl@netgate) * Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index b21611fc..2debcfc2 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1083,12 +1083,12 @@ from_client_msg(clicon_handle h, goto done; } if (clicon_msg_decode(msg, &xt) < 0){ - if (netconf_malformed_message(cbret, "Not recognized, rpc expected")< 0) + if (netconf_malformed_message(cbret, "XML parse error")< 0) goto done; goto reply; } if ((x = xpath_first(xt, "/rpc")) == NULL){ - if (netconf_malformed_message(cbret, "Not recognized, rpc expected")< 0) + if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } @@ -1187,6 +1187,8 @@ from_client_msg(clicon_handle h, if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0) goto done; clicon_debug(1, "%s cbret:%s", __FUNCTION__, cbuf_get(cbret)); + /* XXX problem here is that cbret has not been parsed so may contain + parse errors */ if (send_msg_reply(ce->ce_s, cbuf_get(cbret), cbuf_len(cbret)+1) < 0){ switch (errno){ case EPIPE: diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 8937bfde..6714413a 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -516,12 +516,12 @@ cli_show_config(clicon_handle h, xml2txt(stdout, xc, 0); /* tree-formed text */ break; case FORMAT_CLI: + /* get CLI generatade mode: VARS|ALL */ + if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) + goto done; xc = NULL; /* Dont print xt itself */ - while ((xc = xml_child_each(xt, xc, -1)) != NULL){ - if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) - goto done; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) xml2cli(stdout, xc, NULL, gt); /* cli syntax */ - } break; case FORMAT_NETCONF: fprintf(stdout, "\n"); diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index c1ad1911..aa529477 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -211,7 +211,7 @@ append_listkeys(cbuf *ckey, xml_name(xt), keyname); goto done; } - if (percent_encode(xml_body(xkey), &bodyenc) < 0) + if (uri_percent_encode(xml_body(xkey), &bodyenc) < 0) goto done; if (i++) cprintf(ckey, ","); @@ -328,7 +328,7 @@ get(char *dbname, * If xml element is a leaf-list, then the next element is expected to * be a value */ - if (percent_decode(restval, &argdec) < 0) + if (uri_percent_decode(restval, &argdec) < 0) goto done; if ((xc = xml_find(x, name))==NULL || (xb = xml_find(xc, argdec))==NULL){ @@ -373,7 +373,7 @@ get(char *dbname, if (j>=nvalvec) break; arg = valvec[j++]; - if (percent_decode(arg, &argdec) < 0) + if (uri_percent_decode(arg, &argdec) < 0) goto done; cprintf(cb, "[%s=%s]", cv_string_get(cvi), argdec); free(argdec); @@ -391,7 +391,7 @@ get(char *dbname, break; arg = valvec[j++]; keyname = cv_string_get(cvi); - if (percent_decode(arg, &argdec) < 0) + if (uri_percent_decode(arg, &argdec) < 0) goto done; if (create_keyvalues(xc, ykey, @@ -681,7 +681,7 @@ put(char *dbfile, goto done; break; case Y_LEAF_LIST: - if (percent_encode(body, &bodyenc) < 0) + if (uri_percent_encode(body, &bodyenc) < 0) goto done; cprintf(cbxk, "=%s", bodyenc); break; diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index 86088743..311702b5 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -74,8 +74,9 @@ static inline char * strdup4(char *str) char **clicon_strsep(char *string, char *delim, int *nvec0); char *clicon_strjoin (int argc, char **argv, char *delim); int str2cvec(char *string, char delim1, char delim2, cvec **cvp); -int percent_encode(char *str, char **escp); -int percent_decode(char *esc, char **str); +int uri_percent_encode(char *str, char **escp); +int uri_percent_decode(char *esc, char **str); +int xml_chardata_encode(char *str, char **escp); const char *clicon_int2str(const map_str2int *mstab, int i); int clicon_str2int(const map_str2int *mstab, char *str); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index a2a70fc0..866efec6 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -54,6 +54,7 @@ /* clixon */ #include "clixon_queue.h" #include "clixon_hash.h" +#include "clixon_string.h" #include "clixon_err.h" #include "clixon_handle.h" #include "clixon_yang.h" @@ -73,20 +74,27 @@ netconf_in_use(cbuf *cb, char *type, char *message) { - int retval = -1; - + int retval = -1; + char *encstr = NULL; + if (cprintf(cb, "" "in-use" "%s" "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -105,7 +113,8 @@ netconf_invalid_value(cbuf *cb, char *type, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "invalid-value" @@ -113,12 +122,18 @@ netconf_invalid_value(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -139,6 +154,7 @@ netconf_too_big(cbuf *cb, char *message) { int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "too-big" @@ -146,12 +162,18 @@ netconf_too_big(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -172,7 +194,8 @@ netconf_missing_attribute(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "missing-attribute" @@ -181,8 +204,12 @@ netconf_missing_attribute(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; @@ -208,7 +235,8 @@ netconf_bad_attribute(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "bad-attribute" @@ -217,12 +245,18 @@ netconf_bad_attribute(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -243,7 +277,8 @@ netconf_unknown_attribute(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "unknown-attribute" @@ -252,19 +287,24 @@ netconf_unknown_attribute(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); goto done; } - /*! Create Netconf missing-element error XML tree according to RFC 6241 App A * * An expected element is missing. @@ -279,7 +319,8 @@ netconf_missing_element(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "missing-element" @@ -288,12 +329,18 @@ netconf_missing_element(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -315,7 +362,8 @@ netconf_bad_element(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "bad-element" @@ -324,12 +372,18 @@ netconf_bad_element(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -350,8 +404,8 @@ netconf_unknown_element(cbuf *cb, char *info, char *message) { - int retval = -1; - + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "unknown-element" @@ -360,12 +414,18 @@ netconf_unknown_element(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -386,7 +446,8 @@ netconf_unknown_namespace(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "unknown-namespace" @@ -395,12 +456,18 @@ netconf_unknown_namespace(cbuf *cb, "error", type, info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -420,6 +487,7 @@ netconf_access_denied(cbuf *cb, char *message) { int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "access-denied" @@ -427,12 +495,18 @@ netconf_access_denied(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -484,7 +558,8 @@ netconf_lock_denied(cbuf *cb, char *info, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "lock-denied" @@ -493,12 +568,18 @@ netconf_lock_denied(cbuf *cb, "error", info) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -517,20 +598,27 @@ netconf_resource_denied(cbuf *cb, char *type, char *message) { - int retval = -1; - + int retval = -1; + char *encstr = NULL; + if (cprintf(cb, "" "resource-denied" "%s" "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -550,7 +638,8 @@ netconf_rollback_failed(cbuf *cb, char *type, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "rollback-failed" @@ -558,12 +647,18 @@ netconf_rollback_failed(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -582,19 +677,26 @@ int netconf_data_exists(cbuf *cb, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "data-exists" "application" "error") <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -613,19 +715,26 @@ int netconf_data_missing(cbuf *cb, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "data-missing" "application" "error") <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -645,7 +754,8 @@ netconf_operation_not_supported(cbuf *cb, char *type, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "operation-not-supported" @@ -653,12 +763,18 @@ netconf_operation_not_supported(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -678,7 +794,8 @@ netconf_operation_failed(cbuf *cb, char *type, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "operation-failed" @@ -686,12 +803,18 @@ netconf_operation_failed(cbuf *cb, "error", type) <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") < 0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); @@ -744,19 +867,26 @@ int netconf_malformed_message(cbuf *cb, char *message) { - int retval = -1; + int retval = -1; + char *encstr = NULL; if (cprintf(cb, "" "malformed-message" "rpc" "error") <0) goto err; - if (message && cprintf(cb, "%s", message) < 0) - goto err; + if (message){ + if (xml_chardata_encode(message, &encstr) < 0) + goto done; + if (cprintf(cb, "%s", encstr) < 0) + goto err; + } if (cprintf(cb, "") <0) goto err; retval = 0; done: + if (encstr) + free(encstr); return retval; err: clicon_err(OE_XML, errno, "cprintf"); diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index d02bf5ff..d9892e90 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -140,7 +140,7 @@ clicon_strjoin(int argc, } static int -unreserved(unsigned char in) +uri_unreserved(unsigned char in) { switch(in) { case '0': case '1': case '2': case '3': case '4': @@ -163,12 +163,18 @@ unreserved(unsigned char in) return 0; } -/*! Percent encoding according to RFC 3896 - * @param[out] esc Deallocate with free() +/*! Percent encoding according to RFC 3986 URI Syntax + * @param[in] str Not-encoded input string + * @param[out] escp Encoded/escaped malloced output string + * @retval 0 OK + * @retval -1 Error + * @see RFC 3986 Uniform Resource Identifier (URI): Generic Syntax + * @see uri_percent_decode + * @see xml_chardata_encode */ int -percent_encode(char *str, - char **escp) +uri_percent_encode(char *str, + char **escp) { int retval = -1; char *esc = NULL; @@ -184,7 +190,7 @@ percent_encode(char *str, memset(esc, 0, len); j = 0; for (i=0; i "& " must + * < -> "< " must + * > -> "> " must for backward compatibility + * ' -> "' " may + * ' -> "" " may + * Optionally > + */ +int +xml_chardata_encode(char *str, + char **escp) +{ + int retval = -1; + char *esc = NULL; + int l; + int len; + int i, j; + + len = 0; + for (i=0; i': + len += strlen("> "); + break; + default: + len++; + } + } + len++; /* trailing \0 */ + if ((esc = malloc(len)) == NULL){ + clicon_err(OE_UNIX, errno, "malloc"); + goto done; + } + memset(esc, 0, len); + j = 0; + for (i=0; i': + if ((l=snprintf(&esc[j], 6, "> ")) < 0){ + clicon_err(OE_UNIX, errno, "snprintf"); + goto done; + } + j += l; + break; + default: + esc[j++] = str[i]; + } + } + *escp = esc; + retval = 0; + done: + if (retval < 0 && esc) + free(esc); + return retval; +} + /*! Split a string into a cligen variable vector using 1st and 2nd delimiter * Split a string first into elements delimited by delim1, then into * pairs delimited by delim2. @@ -295,7 +398,7 @@ str2cvec(char *string, *(snext++) = '\0'; if ((val = index(s, delim2)) != NULL){ *(val++) = '\0'; - if (percent_decode(val, &valu) < 0) + if (uri_percent_decode(val, &valu) < 0) goto err; if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ clicon_err(OE_UNIX, errno, "cvec_add"); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 6811e550..8204b04b 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -950,7 +950,7 @@ xml_free(cxobj *x) * XML printing functions. Output a parse tree to file, string cligen buf *------------------------------------------------------------------------*/ -/*! Print an XML tree structure to an output stream +/*! Print an XML tree structure to an output stream and encode chars "<>&" * * Uses clicon_xml2cbuf internally * @@ -975,13 +975,17 @@ clicon_xml2file(FILE *f, int hasbody; int haselement; char *val; - + char *encstr = NULL; /* xml encoded string */ + name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ case CX_BODY: - if ((val = xml_value(x)) != NULL) /* incomplete tree */ - fprintf(f, "%s", xml_value(x)); + if ((val = xml_value(x)) == NULL) /* incomplete tree */ + break; + if (xml_chardata_encode(val, &encstr) < 0) + goto done; + fprintf(f, "%s", encstr); break; case CX_ATTR: fprintf(f, " "); @@ -1044,6 +1048,8 @@ clicon_xml2file(FILE *f, }/* switch */ retval = 0; done: + if (encstr) + free(encstr); return retval; } @@ -1063,8 +1069,7 @@ xml_print(FILE *f, return clicon_xml2file(f, xn, 0, 1); } - -/*! Print an XML tree structure to a cligen buffer +/*! Print an XML tree structure to a cligen buffer and encode chars "<>&" * * @param[in,out] cb Cligen buffer to write to * @param[in] xn Clicon xml tree @@ -1093,12 +1098,18 @@ clicon_xml2cbuf(cbuf *cb, int hasbody; int haselement; char *namespace; - + char *encstr = NULL; /* xml encoded string */ + char *val; + name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ case CX_BODY: - cprintf(cb, "%s", xml_value(x)); + if ((val = xml_value(x)) == NULL) /* incomplete tree */ + break; + if (xml_chardata_encode(val, &encstr) < 0) + goto done; + cprintf(cb, "%s", encstr); break; case CX_ATTR: cprintf(cb, " "); @@ -1130,7 +1141,6 @@ clicon_xml2cbuf(cbuf *cb, default: break; } - /* Check for special case
instead of */ if (hasbody==0 && haselement==0) cprintf(cb, "/>"); @@ -1158,6 +1168,8 @@ clicon_xml2cbuf(cbuf *cb, }/* switch */ retval = 0; done: + if (encstr) + free(encstr); return retval; } /*! Print actual xml tree datastructures (not xml), mainly for debugging diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 701615e8..7741a14e 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -189,7 +189,9 @@ xml2cli(FILE *f, int nr; int i; cbuf *cbpre; + // yang_stmt *ys; + // ys = yang_spec(x); /* Create prepend variable string */ if ((cbpre = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); @@ -212,15 +214,15 @@ xml2cli(FILE *f, } if (prepend0) cprintf(cbpre, "%s", prepend0); -/* bool determines when to print a variable keyword: - !leaf T for all (ie parameter) - index GT_NONE F - index GT_VARS F - index GT_ALL T - !index GT_NONE F - !index GT_VARS T - !index GT_ALL T - */ + /* bool determines when to print a variable keyword: + !leaf T for all (ie parameter) + index GT_NONE F + index GT_VARS F + index GT_ALL T + !index GT_NONE F + !index GT_VARS T + !index GT_ALL T + */ bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS); // bool = (!x->xn_index || gt == GT_ALL); if (bool){ @@ -269,7 +271,6 @@ validate_leafref(cxobj *xt, char *leafrefbody; char *leafbody; - if ((leafrefbody = xml_body(xt)) == NULL) return 0; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ @@ -901,7 +902,7 @@ api_path_fmt2api_path(char *api_path_fmt, clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } - if (percent_encode(str, &strenc) < 0) + if (uri_percent_encode(str, &strenc) < 0) goto done; cprintf(cb, "%s", strenc); free(strenc); strenc = NULL; @@ -1495,7 +1496,7 @@ api_path2xml_vec(char **vec, if ((restval_enc = index(name, '=')) != NULL){ *restval_enc = '\0'; restval_enc++; - if (percent_decode(restval_enc, &restval) < 0) + if (uri_percent_decode(restval_enc, &restval) < 0) goto done; } /* Split into prefix and localname, ignore prefix for now */ diff --git a/lib/src/clixon_xml_parse.h b/lib/src/clixon_xml_parse.h index 83950abb..3122e8f0 100644 --- a/lib/src/clixon_xml_parse.h +++ b/lib/src/clixon_xml_parse.h @@ -51,6 +51,7 @@ struct xml_parse_yacc_arg{ cxobj *ya_xparent; /* xml parent element*/ int ya_skipspace; /* If set, remove all non-terminal bodies (strip pretty-print) */ yang_spec *ya_yspec; /* If set, top-level yang-spec */ + int ya_lex_state; /* lex start condition (AMPERSAND) */ }; extern char *clixon_xml_parsetext; diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index e16ffaae..c6f1726a 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -76,6 +76,7 @@ int clixon_xml_parsewrap(void) %x START %s STATEA +%s AMPERSAND %s CMNT %s STR %s TEXTDECL @@ -88,24 +89,31 @@ int clixon_xml_parsewrap(void) } [ \t]+ ; \: return *clixon_xml_parsetext; -\n { _YA->ya_linenum++;} -""/>" { BEGIN(STATEA); return ESLASH; } -"" { BEGIN(START); return ECOMMENT; } \n _YA->ya_linenum++; @@ -117,15 +125,15 @@ int clixon_xml_parsewrap(void) \" { BEGIN(STRDQ); return *clixon_xml_parsetext; } \' { BEGIN(STRSQ); return *clixon_xml_parsetext; } -[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \" { BEGIN(START); return *clixon_xml_parsetext; } -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } -[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \" { BEGIN(TEXTDECL); return *clixon_xml_parsetext; } -1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } -[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHAR; } +1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } +[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; } \' { BEGIN(TEXTDECL); return *clixon_xml_parsetext; } %% diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index 1e774e29..ab9d5193 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -41,7 +41,7 @@ %start topxml -%token NAME CHAR +%token NAME CHARDATA %token VER ENC %token BSLASH ESLASH %token BTEXT ETEXT @@ -329,15 +329,15 @@ topxml : list dcl : BTEXT info encode ETEXT { clicon_debug(3, "dcl->info encode"); } ; -info : VER '=' '\"' CHAR '\"' +info : VER '=' '\"' CHARDATA '\"' { if (xml_parse_version(_YA, $4) <0) YYABORT; } - | VER '=' '\'' CHAR '\'' + | VER '=' '\'' CHARDATA '\'' { if (xml_parse_version(_YA, $4) <0) YYABORT; } | ; -encode : ENC '=' '\"' CHAR '\"' {free($4);} - | ENC '=' '\'' CHAR '\'' {free($4);} +encode : ENC '=' '\"' CHARDATA '\"' {free($4);} + | ENC '=' '\'' CHARDATA '\'' {free($4);} ; element : '<' qname attrs element1 @@ -372,8 +372,8 @@ list : list content { clicon_debug(3, "list -> list content"); } content : element { clicon_debug(3, "content -> element"); } | comment { clicon_debug(3, "content -> comment"); } - | CHAR { if (xml_parse_content(_YA, $1) < 0) YYABORT; - clicon_debug(3, "content -> CHAR %s", $1); } + | CHARDATA { if (xml_parse_content(_YA, $1) < 0) YYABORT; + clicon_debug(3, "content -> CHARDATA %s", $1); } | { clicon_debug(3, "content -> "); } ; @@ -394,7 +394,7 @@ attqname : NAME {$$ = $1;} ; -attvalue : '\"' CHAR '\"' { $$=$2; /* $2 must be consumed */} +attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */} | '\"' '\"' { $$=strdup(""); /* $2 must be consumed */} ; diff --git a/test/test_cli.sh b/test/test_cli.sh index 338ba060..51a2ea5e 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -60,6 +60,11 @@ expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" "^$" new "cli show configuration" expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" +new "cli configure using encoded chars data <&" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" "" +new "cli configure using encoded chars name <&" +expectfn "$clixon_cli -1 -f $cfg set interfaces interface fddi&< type eth" "" + new "cli failed validate" expectfn "$clixon_cli -1 -f $cfg -l o validate" "Missing mandatory variable" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 9877c770..5b872e67 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -98,11 +98,17 @@ expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" -new "netconf edit config replace XXX is merge?" +new "netconf edit config merge" expecteof "$clixon_netconf -qf $cfg" "eth2ethmerge]]>]]>" "^]]>]]>$" +new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" +expecteof "$clixon_netconf -qf $cfg" "eth& t< > ]]>]]>" "^]]>]]>$" + new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth1ethtrueeth2ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" + +new "cli show configuration eth& - encoding tests" +expectfn "$clixon_cli -1 -f $cfg show conf cli" "interfaces interface name eth&" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" diff --git a/test/test_type.sh b/test/test_type.sh index 8eaaba92..7788ee02 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -8,7 +8,6 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/type.yang - cat < $cfg $cfg @@ -164,7 +163,7 @@ new "netconf validate ok" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf set ab wrong" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "a.b&c.d]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "a.b& c.d]]>]]>" "^]]>]]>$" new "netconf validate" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" From 3ed1c985569f3e21b42b36eaaa6946e59ca912d0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 22 Apr 2018 16:53:12 +0200 Subject: [PATCH 40/50] * Fixed issue https://github.com/clicon/clixon/issues/18 RPC response issues reported by Stephen Jones at Netgate --- CHANGELOG.md | 4 +- apps/backend/backend_client.c | 2 +- apps/backend/backend_plugin.c | 45 +++++------ apps/backend/backend_plugin.h | 2 +- apps/restconf/restconf_methods.c | 69 +++++++++++++---- example/example_backend.c | 39 ++++++---- example/example_backend_nacm.c | 7 +- lib/src/clixon_xml_map.c | 1 + lib/src/clixon_yang.c | 14 ++-- test/test_netconf.sh | 125 ++++++++++++++++++++----------- test/test_restconf.sh | 20 ++++- 11 files changed, 215 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf2c9a9..01675118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,7 +103,9 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs -* Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs +* Fixed issue https://github.com/clicon/clixon/issues/18 RPC response issues reported by Stephen Jones at Netgate +* Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs reported by David Cornejo at Netgate. + * This was a large rewright of XML parsing and output due to CharData not correctly encoded according to https://www.w3.org/TR/2008/REC-xml-20081126. * Fixed three-key list entry problem (reported by jdl@netgate) * Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 2debcfc2..1f8ac9c2 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -289,7 +289,7 @@ from_client_get(clicon_handle h, /* Get state data from plugins as defined by plugin_statedata(), if any */ assert(xret); clicon_err_reset(); - if ((ret = clixon_plugin_statedata(h, selector, xret)) < 0) + if ((ret = clixon_plugin_statedata(h, selector, &xret)) < 0) goto done; if (ret == 0){ /* OK */ cprintf(cbret, ""); diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 80d0f128..e69c21fa 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -111,18 +111,18 @@ clixon_plugin_reset(clicon_handle h, /*! Go through all backend statedata callbacks and collect state data * This is internal system call, plugin is invoked (does not call) this function * Backend plugins can register - * @param[in] h clicon handle - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[in,out] xml XML tree. - * @param[out] cbret Return xml value cligen buffer - * @retval -1 Error - * @retval 0 OK - * @retval 1 Statedata callback failed + * @param[in] h clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in,out] xtop State XML tree is merged with existing tree. + * @retval -1 Error + * @retval 0 OK + * @retval 1 Statedata callback failed + * @note xtop can be replaced */ int clixon_plugin_statedata(clicon_handle h, char *xpath, - cxobj *xtop) + cxobj **xtop) { int retval = -1; int i; @@ -139,7 +139,7 @@ clixon_plugin_statedata(clicon_handle h, clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } - if (xtop==NULL){ + if (*xtop==NULL){ clicon_err(OE_CFG, ENOENT, "XML tree expected"); goto done; } @@ -152,22 +152,14 @@ clixon_plugin_statedata(clicon_handle h, retval = 1; goto done; /* Dont quit here on user callbacks */ } - if (xml_merge(xtop, x, yspec, &reason) < 0) + if (xml_merge(*xtop, x, yspec, &reason) < 0) goto done; if (reason){ - cbuf *cb; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); + while ((xc = xml_child_i(*xtop, 0)) != NULL) + xml_purge(xc); + if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0) goto done; - } - if (netconf_operation_failed(cb, "rpc", reason)< 0) - goto done; - while ((xc = xml_child_i(xtop, 0)) != NULL) - xml_purge(xc); - if (xml_parse_string(cbuf_get(cb), NULL, &xtop) < 0) - goto done; - cbuf_free(cb); - break; + goto ok; } if (x){ xml_free(x); @@ -175,7 +167,7 @@ clixon_plugin_statedata(clicon_handle h, } } /* Code complex to filter out anything that is outside of xpath */ - if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + if (xpath_vec(*xtop, xpath?xpath:"/", &xvec, &xlen) < 0) goto done; /* If vectors are specified then mark the nodes found and @@ -187,12 +179,13 @@ clixon_plugin_statedata(clicon_handle h, xml_flag_set(xvec[i], XML_FLAG_MARK); } /* Remove everything that is not marked */ - if (!xml_flag(xtop, XML_FLAG_MARK)) - if (xml_tree_prune_flagged_sub(xtop, XML_FLAG_MARK, 1, NULL) < 0) + if (!xml_flag(*xtop, XML_FLAG_MARK)) + if (xml_tree_prune_flagged_sub(*xtop, XML_FLAG_MARK, 1, NULL) < 0) goto done; /* reset flag */ - if (xml_apply(xtop, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) + if (xml_apply(*xtop, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; + ok: retval = 0; done: if (reason) diff --git a/apps/backend/backend_plugin.h b/apps/backend/backend_plugin.h index 3e9f4d1d..3dff37b2 100644 --- a/apps/backend/backend_plugin.h +++ b/apps/backend/backend_plugin.h @@ -71,7 +71,7 @@ int backend_plugin_initiate(clicon_handle h); int clixon_plugin_reset(clicon_handle h, char *db); -int clixon_plugin_statedata(clicon_handle h, char *xpath, cxobj *xml); +int clixon_plugin_statedata(clicon_handle h, char *xpath, cxobj **xtop); transaction_data_t * transaction_new(void); int transaction_free(transaction_data_t *); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index ae233df3..b055f831 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1003,6 +1003,13 @@ api_operations_post(clicon_handle h, } for (i=0; i */ - if ((xinput = xpath_first(xdata, "/input")) != NULL){ + if ((xinput = xpath_first(xdata, "/input")) == NULL){ + xml_name_set(xdata, "input"); + xml_spec_set(xdata, yinput); /* needed for xml_spec_populate */ + if (yinput){ + if (xml_yang_validate_add(xdata, NULL) < 0){ + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + } + } + else{ /* Add all input under path */ x = NULL; while (xml_child_nr(xinput)){ @@ -1065,15 +1086,20 @@ api_operations_post(clicon_handle h, if (xml_addsub(xbot, x) < 0) goto done; } - if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ - xml_spec_set(xinput, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xinput, CX_ELMNT, xml_spec_populate, yinput) < 0) + if (yinput){ + xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ + if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yinput) < 0) goto done; - if (xml_apply(xinput, CX_ELMNT, + if (xml_apply(xbot, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) goto done; - if (xml_yang_validate_add(xinput, NULL) < 0) - goto done; + if (xml_yang_validate_add(xbot, NULL) < 0){ + if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0) + goto done; + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } } } } @@ -1099,15 +1125,30 @@ api_operations_post(clicon_handle h, } break; /* Just one if local */ } - if (ret == 0) /* Send to backend */ + if (ret == 0){ /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; + if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + goto done; + goto ok; + } + } + /* Check if RPC output section */ + if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) == NULL){ + /* If the RPC operation is invoked without errors and if the "rpc" or + * "action" statement has no "output" section, the response message + * MUST NOT include a message-body and MUST send a "204 No Content" + * status-line instead. + */ + FCGX_SetExitStatus(204, r->out); /* OK */ + FCGX_FPrintF(r->out, "\r\n"); + goto ok; + } if ((cbx = cbuf_new()) == NULL) goto done; - if ((xoutput=xpath_first(xret, "/")) != NULL) - xml_name_set(xoutput, "output"); - if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL && - xoutput){ + if ((xoutput=xpath_first(xret, "/")) != NULL){ + xml_name_set(xoutput, "output"); #if 0 clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); #endif @@ -1134,7 +1175,7 @@ api_operations_post(clicon_handle h, if (xml2json_cbuf(cbx, xoutput, pretty) < 0) goto done; #if 1 - clicon_debug(1, "%s xoutput:%s", __FUNCTION__, cbuf_get(cbx)); + clicon_debug(1, "%s cbx:%s", __FUNCTION__, cbuf_get(cbx)); #endif FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); diff --git a/example/example_backend.c b/example/example_backend.c index 28f5611e..696f8660 100644 --- a/example/example_backend.c +++ b/example/example_backend.c @@ -121,7 +121,9 @@ notification_timer_setup(clicon_handle h) return event_reg_timeout(t, notification_timer, h, "notification timer"); } -/*! IETF Routing fib-route rpc */ +/*! IETF Routing fib-route rpc + * @see ietf-routing@2014-10-26.yang (fib-route) + */ static int fib_route(clicon_handle h, /* Clicon handle */ cxobj *xe, /* Request: */ @@ -136,19 +138,9 @@ fib_route(clicon_handle h, /* Clicon handle */ return 0; } -/*! Smallest possible RPC declaration for test */ -static int -empty(clicon_handle h, /* Clicon handle */ - cxobj *xe, /* Request: */ - cbuf *cbret, /* Reply eg ... */ - void *arg, /* client_entry */ - void *regarg) /* Argument given at register */ -{ - cprintf(cbret, ""); - return 0; -} - -/*! IETF Routing route-count rpc */ +/*! IETF Routing route-count rpc + * @see ietf-routing@2014-10-26.yang (route-count) + */ static int route_count(clicon_handle h, cxobj *xe, /* Request: */ @@ -156,7 +148,24 @@ route_count(clicon_handle h, void *arg, void *regarg) /* Argument given at register */ { - cprintf(cbret, ""); + cprintf(cbret, "42"); + return 0; +} + +/*! Smallest possible RPC declaration for test + * Yang/XML: + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element defined + * in [RFC6241]. + */ +static int +empty(clicon_handle h, /* Clicon handle */ + cxobj *xe, /* Request: */ + cbuf *cbret, /* Reply eg ... */ + void *arg, /* client_entry */ + void *regarg) /* Argument given at register */ +{ + cprintf(cbret, ""); return 0; } diff --git a/example/example_backend_nacm.c b/example/example_backend_nacm.c index cdc7346a..d6ed4600 100644 --- a/example/example_backend_nacm.c +++ b/example/example_backend_nacm.c @@ -87,7 +87,6 @@ nacm_statedata(clicon_handle h, return retval; } - int plugin_start(clicon_handle h, int argc, @@ -103,8 +102,8 @@ static clixon_plugin_api api = { clixon_plugin_init, /* init */ plugin_start, /* start */ NULL, /* exit */ - .ca_reset=NULL, /* reset */ /*--- Backend plugin only ---*/ - .ca_statedata=nacm_statedata, /* statedata */ + .ca_reset=NULL, /* reset */ + .ca_statedata=nacm_statedata, /* statedata */ }; /*! Backend plugin initialization @@ -115,6 +114,6 @@ static clixon_plugin_api api = { clixon_plugin_api * clixon_plugin_init(clicon_handle h) { - clicon_debug(1, "%s backend secondary", __FUNCTION__); + clicon_debug(1, "%s backend nacm", __FUNCTION__); return &api; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 7741a14e..917131f7 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -322,6 +322,7 @@ xml_yang_validate_add(cxobj *xt, and !Node has a config sub-statement and it is false */ if ((ys = xml_spec(xt)) != NULL && yang_config(ys) != 0){ switch (ys->ys_keyword){ + case Y_INPUT: case Y_LIST: /* fall thru */ case Y_CONTAINER: diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index c704134f..fe119a6f 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -32,7 +32,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 + */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ @@ -1975,8 +1977,9 @@ schema_nodeid_vec(yang_node *yn, /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec * @param[in] yspec Yang specification. * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid + * @param[out] yres Result yang statement node, or NULL if not found + * @retval -1 Error, with clicon_err called + * @retval 0 OK (if yres set then found, if yres=0 then not found) * Assume schema nodeid:s have prefixes, (actually the first). * @see yang_desc_schema_nodeid * Used in yang: deviation, top-level augment @@ -2011,8 +2014,8 @@ yang_abs_schema_nodeid(yang_spec *yspec, } /* split : */ if ((id = strchr(vec[1], ':')) == NULL){ /* no prefix */ - clicon_err(OE_YANG, 0, "Absolute schema nodeid must have prefix"); - goto done; + clicon_log(LOG_WARNING, "%s: Absolute schema nodeid must have prefix", __FUNCTION__); + goto ok; } if ((prefix = strdup(vec[1])) == NULL){ clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__); @@ -2040,6 +2043,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, } if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) goto done; + ok: /* yres may not be set */ retval = 0; done: if (vec) diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 5b872e67..7b66e88e 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -5,14 +5,16 @@ APPNAME=example . ./lib.sh cfg=$dir/conf_yang.xml +fyang=$dir/netconf.yang cat < $cfg $cfg /usr/local/share/$APPNAME/yang - example + $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/backend + example_backend.so$ /usr/local/lib/$APPNAME/netconf /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli @@ -25,138 +27,175 @@ cat < $cfg EOF +cat < $fyang +module example{ + prefix ex; + import ietf-ip { + prefix ip; + } + import ietf-routing { + prefix rt; + } + import ietf-inet-types { + prefix "inet"; + revision-date "2013-07-15"; + } + rpc empty { + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } +} +EOF + # kill old backend (if any) new "kill old backend" sudo clixon_backend -zf $cfg if [ $? -ne 0 ]; then err fi -new "start backend -s init -f $cfg" +new "start backend -s init -f $cfg -y $fyang" # start new backend -sudo clixon_backend -s init -f $cfg # -D 1 +sudo clixon_backend -s init -f $cfg -y $fyang # -D 1 if [ $? -ne 0 ]; then err fi new "netconf get-config" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none which should not change anything" -expecteof "$clixon_netconf -qf $cfg" "noneeth/0/0]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "noneeth/0/0]]>]]>" "^]]>]]>$" new "Check nothing added" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Add subtree eth/0/0 using none and create which should add eth/0/0" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" new "Check eth/0/0 added using xpath" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth/0/0ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0ethtrue]]>]]>$" new "Re-create same eth/0/0 which should generate error" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" new "Delete eth/0/0 using none config" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^]]>]]>$" new "Check deleted eth/0/0 (non-presence container)" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' '^]]>]]>$' +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' '^]]>]]>$' new "Re-Delete eth/0/0 using none should generate error" -expecteof "$clixon_netconf -qf $cfg" 'eth/0/0ethnone ]]>]]>' "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 'eth/0/0ethnone ]]>]]>' "^" new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg" "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth/0/0eth1true
9.2.3.424
]]>]]>" "^]]>]]>$" new "netconf get config xpath" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth1true]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth1true]]>]]>$" new "netconf get config xpath parent" -expecteof "$clixon_netconf -qf $cfg" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" ']]>]]>' "^eth/0/0trueeth1truetruefalse
9.2.3.424
]]>]]>$" new "netconf validate missing type" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get empty config2" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit extra xml" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit config eth1" -expecteof "$clixon_netconf -qf $cfg" "eth1eth]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^]]>]]>$" new "netconf validate" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf commit" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit config merge" -expecteof "$clixon_netconf -qf $cfg" "eth2ethmerge]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth2ethmerge]]>]]>" "^]]>]]>$" new "netconf edit ampersand encoding(<&): name:'eth&' type:'t<>'" -expecteof "$clixon_netconf -qf $cfg" "eth& t< > ]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth& t< > ]]>]]>" "^]]>]]>$" new "netconf get replaced config" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" new "cli show configuration eth& - encoding tests" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "interfaces interface name eth&" +expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" "interfaces interface name eth&" new "netconf discard-changes" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf edit state operation should fail" -expecteof "$clixon_netconf -qf $cfg" "eth1eth]]>]]>" "^invalid-value" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "eth1eth]]>]]>" "^invalid-value" new "netconf get state operation" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth0eth42]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth0eth42]]>]]>$" new "netconf lock/unlock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" new "netconf lock/lock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf lock" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "close-session" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "kill-session" -expecteof "$clixon_netconf -qf $cfg" "44]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "44]]>]]>" "^]]>]]>$" new "copy startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf get startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^eth1ethtrue]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth1ethtrue]]>]]>$" new "netconf delete startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf check empty startup" -expecteof "$clixon_netconf -qf $cfg" "]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" new "netconf rpc w/o namespace" -expecteof "$clixon_netconf -qf $cfg" "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "ipv4ipv4]]>]]>" "^ipv4" + +new "netconf empty rpc" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" new "netconf client-side rpc" -expecteof "$clixon_netconf -qf $cfg" "example]]>]]>" "^ok]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "example]]>]]>" "^ok]]>]]>$" new "netconf subscription" -expectwait "$clixon_netconf -qf $cfg" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 +expectwait "$clixon_netconf -qf $cfg -y $fyang" "ROUTING]]>]]>" "^]]>]]>Routing notification]]>]]>$" 30 new "Kill backend" # Check if still alive diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 7901ea9c..bee9866f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -86,7 +86,7 @@ new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -D +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang # -D sleep 1 @@ -137,8 +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)" '{"output": null} - ' +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}]}}} @@ -244,6 +243,21 @@ 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"}}}} ' +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"}}}} ' + +new2 "restconf rpc non-existing rpc" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations/ex:kalle)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "yang node not found"}}}} ' + +new2 "restconf rpc missing name" +expecteq "$(curl -s -X POST -d '{}' http://localhost/restconf/operations)" '{"ietf-restconf:errors" : {"error": {"rpc-error": {"error-tag": "operation-failed","error-type": "protocol","error-severity": "error","error-message": "Operation name expected"}}}} ' + +new2 "restconf rpc missing input" +expecteq "$(curl -s -X POST -d '{}' 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"}}}} ' + new "restconf rpc using POST xml" ret=$(curl -s -X POST -H "Accept: application/yang-data+xml" -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route) expect="ipv42.3.4.5" From 1913407e52130bca89ba3466c1550ead06e835d5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 22 Apr 2018 20:13:54 +0200 Subject: [PATCH 41/50] Replacing remaining badrequest() with proper restconf error msg. --- apps/restconf/restconf_methods.c | 50 +++++++++++++++---- doc/Doxyfile | 2 +- doc/Doxyfile.graphs | 2 +- example/README.md | 25 ++++------ example/example_backend.c | 14 +++--- lib/clixon/clixon_netconf_lib.h | 1 + lib/clixon/clixon_options.h | 14 ++++-- lib/clixon/clixon_yang.h | 3 ++ lib/src/clixon_netconf_lib.c | 82 ++++++++++++++++++++++++-------- lib/src/clixon_yang_parse.h | 5 +- lib/src/clixon_yang_parse.l | 9 ++-- lib/src/clixon_yang_parse.y | 6 +-- test/test_restconf.sh | 13 +++-- test/test_restconf2.sh | 2 +- 14 files changed, 153 insertions(+), 75 deletions(-) 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 From 50522df3d9f685233df653fc19bb5cea9fbfaa6f Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 22 Apr 2018 21:36:55 +0200 Subject: [PATCH 42/50] Showing syntax using CLI commands was broekn and is fixed. --- CHANGELOG.md | 1 + lib/clixon/clixon_options.h | 3 +- lib/src/clixon_xml_map.c | 107 ++++++++++++----------------- test/test_cli.sh | 5 +- test/test_netconf.sh | 3 +- yang/clixon-config@2018-02-12.yang | 18 ++++- 6 files changed, 67 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01675118..e56752d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Added cli_show_version() ### Corrected Bugs +* Showing syntax using CLI commands was broekn and is fixed. * Fixed issue https://github.com/clicon/clixon/issues/18 RPC response issues reported by Stephen Jones at Netgate * Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs reported by David Cornejo at Netgate. * This was a large rewright of XML parsing and output due to CharData not correctly encoded according to https://www.w3.org/TR/2008/REC-xml-20081126. diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index f6854d15..1f95c73a 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -50,7 +50,6 @@ /*! Controls how keywords a generated in CLI syntax / prints from object model * Example YANG: - * list a {a.b[] $!x $y: * list a { * key x; * leaf x; @@ -63,7 +62,7 @@ enum genmodel_type{ GT_ERR =-1, /* Error */ GT_NONE=0, /* No extra keywords */ - GT_VARS, /* Keywords on non-index variables */ + GT_VARS, /* Keywords on non-key variables */ GT_ALL, /* Keywords on all variables */ }; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 917131f7..8524a413 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -89,19 +89,6 @@ #include "clixon_xml_sort.h" #include "clixon_xml_map.h" -/* - * A node is a leaf if it contains a body. - */ -static cxobj * -leaf(cxobj *xn) -{ - cxobj *xc = NULL; - - while ((xc = xml_child_each(xn, xc, CX_BODY)) != NULL) - break; - return xc; -} - /*! x is element and has eactly one child which in turn has none */ static int tleaf(cxobj *x) @@ -118,6 +105,7 @@ tleaf(cxobj *x) /*! Translate XML -> TEXT * @param[in] level print 4 spaces per level in front of each line + * XXX rewrite using YANG and remove encrypted password KLUDGE */ int xml2txt(FILE *f, @@ -184,69 +172,60 @@ xml2cli(FILE *f, { int retval = -1; cxobj *xe = NULL; - char *term; - int bool; - int nr; - int i; - cbuf *cbpre; - // yang_stmt *ys; + cbuf *cbpre = NULL; + yang_stmt *ys; + int match; + char *body; - // ys = yang_spec(x); + ys = xml_spec(x); + if (ys->ys_keyword == Y_LEAF || ys->ys_keyword == Y_LEAF_LIST){ + if (prepend0) + fprintf(f, "%s", prepend0); + body = xml_body(x); + if (gt == GT_ALL || gt == GT_VARS) + fprintf(f, "%s ", xml_name(x)); + if (index(body, ' ')) + fprintf(f, "\"%s\"", body); + else + fprintf(f, "%s", body); + fprintf(f, "\n"); + goto ok; + } /* Create prepend variable string */ if ((cbpre = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } - nr = xml_child_nr(x); - if (!nr){ - if (xml_type(x) == CX_BODY) - term = xml_value(x); - else - term = xml_name(x); - if (prepend0) - fprintf(f, "%s ", prepend0); - if (index(term, ' ')) - fprintf(f, "\"%s\"\n", term); - else - fprintf(f, "%s\n", term); - retval = 0; - goto done; - } if (prepend0) cprintf(cbpre, "%s", prepend0); - /* bool determines when to print a variable keyword: - !leaf T for all (ie parameter) - index GT_NONE F - index GT_VARS F - index GT_ALL T - !index GT_NONE F - !index GT_VARS T - !index GT_ALL T - */ - bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS); -// bool = (!x->xn_index || gt == GT_ALL); - if (bool){ - if (cbuf_len(cbpre)) - cprintf(cbpre, " "); - cprintf(cbpre, "%s", xml_name(x)); - } - xe = NULL; - /* First child is unique, then add that, before looping. */ - i = 0; - while ((xe = xml_child_each(x, xe, -1)) != NULL){ - /* Dont call this if it is index and there are other following */ - if (0 && i < nr-1) - ; - else - if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) + cprintf(cbpre, "%s ", xml_name(x)); + + if (ys->ys_keyword == Y_LIST){ + /* If list then first loop through keys */ + xe = NULL; + while ((xe = xml_child_each(x, xe, -1)) != NULL){ + if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0) goto done; - if (0){ /* assume index is first, otherwise need one more while */ + if (!match) + continue; if (gt == GT_ALL) - cprintf(cbpre, " %s", xml_name(xe)); - cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0))); + cprintf(cbpre, "%s ", xml_name(xe)); + cprintf(cbpre, "%s ", xml_body(xe)); } - i++; } + /* Then loop through all other (non-keys) */ + xe = NULL; + while ((xe = xml_child_each(x, xe, -1)) != NULL){ + if (ys->ys_keyword == Y_LIST){ + if ((match = yang_key_match((yang_node*)ys, xml_name(xe))) < 0) + goto done; + if (match) + continue; + } + if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) + goto done; + } + ok: retval = 0; done: if (cbpre) diff --git a/test/test_cli.sh b/test/test_cli.sh index 51a2ea5e..ec71a707 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -58,7 +58,7 @@ new "cli configure" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" "^$" new "cli show configuration" -expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" +expectfn "$clixon_cli -1 -f $cfg show conf cli" "^interfaces interface eth/0/0 enabled true" new "cli configure using encoded chars data <&" expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" "" @@ -101,7 +101,8 @@ new "cli load" expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" "^$" new "cli check load" -expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" "^interfaces interface name eth/0/0" "interfaces interface enabled true$" +expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" "^interfaces interface name eth/0/0 type bgp +interfaces interface eth/0/0 ipv4 enabled true" new "cli debug" expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" "^$" diff --git a/test/test_netconf.sh b/test/test_netconf.sh index 7b66e88e..b6fc58ac 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -144,7 +144,8 @@ new "netconf get replaced config" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^eth& t< > trueeth1ethtrueeth2ethtrue]]>]]>$" new "cli show configuration eth& - encoding tests" -expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" "interfaces interface name eth&" +expectfn "$clixon_cli -1 -f $cfg -y $fyang show conf cli" "interfaces interface eth& type t<> +interfaces interface eth& enabled true" new "netconf discard-changes" expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 58ff73f8..84dc8622 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -82,6 +82,22 @@ module clixon-config { } } } + typedef cli_genmodel_type{ + description + "How to generate CLI from YANG model, + eg list a{ key x; leaf x; leaf y;}"; + type enumeration{ + enum NONE{ + description "No extra keywords: a "; + } + enum VARS{ + description "Keywords on non-key variables: a y "; + } + enum ALL{ + description "Keywords on all variables: a x y "; + } + } + } typedef nacm_mode{ description "Mode of RFC8341 Network Configuration Access Control Model. @@ -204,7 +220,7 @@ module clixon-config { description "Generate code for CLI completion of existing db symbols"; } leaf CLICON_CLI_GENMODEL_TYPE { - type string; + type cli_genmodel_type; default "VARS"; description "How to generate and show CLI syntax: VARS|ALL"; } From dabf0e59189d77c266ce59271ad870b139f2f3a1 Mon Sep 17 00:00:00 2001 From: Renato Botelho do Couto Date: Mon, 23 Apr 2018 13:28:08 -0500 Subject: [PATCH 43/50] Improve permissions and strip binaries - Use 0755 for directories - Use 0644 for libraries, includes and shared files - Use -s (strip) parameter when installing binaries and libraries --- Makefile.in | 4 ++-- apps/backend/Makefile.in | 14 +++++++------- apps/cli/Makefile.in | 14 +++++++------- apps/netconf/Makefile.in | 12 ++++++------ apps/restconf/Makefile.in | 4 ++-- datastore/Makefile.in | 4 ++-- datastore/keyvalue/Makefile.in | 4 ++-- datastore/text/Makefile.in | 4 ++-- etc/Makefile.in | 4 ++-- example/Makefile.in | 30 +++++++++++++++--------------- lib/clixon/Makefile.in | 4 ++-- lib/src/Makefile.in | 4 ++-- yang/Makefile.in | 4 ++-- 13 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Makefile.in b/Makefile.in index 3041750f..3466457d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -69,8 +69,8 @@ clixon.mk: clixon.mk.cpp $(CPP) -P -traditional-cpp -x assembler-with-cpp -Dprefix=$(prefix) -Dlocalstatedir=$(localstatedir) -Dsysconfdir=$(sysconfdir) -Ddatadir=$(datadir) -Dlibdir=$(libdir) $< > $@ install: clixon.mk - install -d -m 755 $(DESTDIR)$(datadir)/clixon - install -m 755 clixon.mk $(DESTDIR)$(datadir)/clixon + install -d -m 0755 $(DESTDIR)$(datadir)/clixon + install -m 0644 clixon.mk $(DESTDIR)$(datadir)/clixon for i in $(SUBDIRS) doc; \ do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done; \ echo "Install for compilation by: make install-include" diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index 2147bb81..d5ce35b2 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -98,15 +98,15 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(sbindir) - install $(APPL) $(DESTDIR)$(sbindir) + install -d -m 0755 $(DESTDIR)$(sbindir) + install -m 0755 -s $(APPL) $(DESTDIR)$(sbindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_config.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_config.so - install -d $(DESTDIR)$(libdir)/clixon/plugins/backend + install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/backend uninstall: rm -f $(DESTDIR)$(sbindir)/$(APPL) @@ -114,8 +114,8 @@ uninstall: rm -f $(DESTDIR)$(includedir)/clixon/* install-include: clixon_backend.h clixon_backend_handle.h clixon_backend_transaction.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon .SUFFIXES: .SUFFIXES: .c .o diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index bdfd9bb9..1272bc83 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -102,19 +102,19 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0644 -s $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_cli.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_cli.so - install -d $(DESTDIR)$(libdir)/clixon/plugins/cli + install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/cli install-include: clixon_cli.h clixon_cli_api.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(bindir)/$(APPL) diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index 21b2d907..b9e0b1a0 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -100,18 +100,18 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_netconf.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_netconf.so install-include: clixon_netconf.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(bindir)/$(APPL) diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index e3b95cfb..8999c847 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -97,8 +97,8 @@ distclean: clean # Also create a libexec/ directory for writeable/temporary files. # Put config file in etc/ install: install-lib $(APPL) - install -d $(DESTDIR)$(wwwdir) - install $(APPL) $(DESTDIR)$(wwwdir) + install -d -m 0755 $(DESTDIR)$(wwwdir) + install -m 0755 -s $(APPL) $(DESTDIR)$(wwwdir) install-lib: $(MYLIB) install -d $(DESTDIR)$(libdir) diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 697bce7c..2e94305e 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -101,8 +101,8 @@ install-include: do (cd $$i ; $(MAKE) $(MFLAGS) $@)||exit 1; done; install: $(APPL) - install -d $(DESTDIR)$(bindir) - install $(APPL) $(DESTDIR)$(bindir) + install -d -m 0755 $(DESTDIR)$(bindir) + install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) for i in $(SUBDIRS); \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in index b18501b7..57d091ff 100644 --- a/datastore/keyvalue/Makefile.in +++ b/datastore/keyvalue/Makefile.in @@ -80,8 +80,8 @@ distclean: clean $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< install: $(PLUGIN) - install -d $(DESTDIR)$(libdir)/xmldb - install $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -d -m 0755 $(DESTDIR)$(libdir)/xmldb + install -m 0644 -s $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index ad24c6e9..cd48ed50 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -84,8 +84,8 @@ distclean: clean $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< install: $(PLUGIN) - install -d $(DESTDIR)$(libdir)/xmldb - install $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -d -m 0755 $(DESTDIR)$(libdir)/xmldb + install -m 0644 -s $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/etc/Makefile.in b/etc/Makefile.in index f124dc52..263840df 100644 --- a/etc/Makefile.in +++ b/etc/Makefile.in @@ -48,8 +48,8 @@ distclean: clean rm -f Makefile *~ .depend clixonrc install: clixonrc - install -m 755 -d $(DESTDIR)$(sysconfdir) - install -m 755 clixonrc $(DESTDIR)$(sysconfdir) + install -m 0755 -d $(DESTDIR)$(sysconfdir) + install -m 0644 clixonrc $(DESTDIR)$(sysconfdir) install-include: diff --git a/example/Makefile.in b/example/Makefile.in index 418e7b07..b043dbef 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -111,21 +111,21 @@ distclean: clean (cd docker && $(MAKE) $(MFLAGS) $@) install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml - install -d $(DESTDIR)$(clixon_SYSCONFDIR) - install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) - install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang - install $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang - install -d $(DESTDIR)$(clixon_LIBDIR)/cli - install $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli; - install -d $(DESTDIR)$(clixon_LIBDIR)/backend - install $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend; - install -d $(DESTDIR)$(clixon_LIBDIR)/netconf - install $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf; - install -d $(DESTDIR)$(clixon_LIBDIR)/restconf - install $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf; - install -d $(DESTDIR)$(clixon_LIBDIR)/clispec - install $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec; - install -d $(DESTDIR)$(clixon_LOCALSTATEDIR) + install -d -m 0755 $(DESTDIR)$(clixon_SYSCONFDIR) + install -m 0644 $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR) + install -d -m 0755 $(DESTDIR)$(clixon_DBSPECDIR)/yang + install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/cli + install -m 0644 -s $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/backend + install -m 0644 -s $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/netconf + install -m 0644 -s $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/restconf + install -m 0644 $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf + install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/clispec + install -m 0644 $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec + install -d -m 0755 $(DESTDIR)$(clixon_LOCALSTATEDIR) (cd docker && $(MAKE) $(MFLAGS) $@) uninstall: diff --git a/lib/clixon/Makefile.in b/lib/clixon/Makefile.in index 0cfe88e8..e455a860 100644 --- a/lib/clixon/Makefile.in +++ b/lib/clixon/Makefile.in @@ -44,8 +44,8 @@ depend: install: install-include: - install -m 755 -d $(DESTDIR)$(includedir)/clixon - install -m 644 *.h $(DESTDIR)$(includedir)/clixon + install -m 0755 -d $(DESTDIR)$(includedir)/clixon + install -m 0644 *.h $(DESTDIR)$(includedir)/clixon uninstall: rm -rf $(DESTDIR)$(includedir)/clixon diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 07e3b4a5..8d714200 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -181,8 +181,8 @@ install: install-lib install-include: install-lib: $(MYLIB) - install -m 755 -d $(DESTDIR)$(libdir) - install -m 755 $(MYLIB) $(DESTDIR)$(libdir) + install -m 0755 -d $(DESTDIR)$(libdir) + install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon.so.3 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon.so diff --git a/yang/Makefile.in b/yang/Makefile.in index 28a397c1..e4a8aaf9 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -58,8 +58,8 @@ distclean: clean install: $(YANGSPECS) echo $(DESTDIR)$(datarootdir)/clixon/clixon.mk echo $(DESTDIR)$(clixon_DATADIR) - install -d $(DESTDIR)$(clixon_DATADIR) - install $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) + install -d -m 0755 $(DESTDIR)$(clixon_DATADIR) + install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DATADIR) uninstall: (cd $(DESTDIR)$(clixon_DATADIR); rm -rf $(YANGSPECS)) From 513df9b19d8b3694e911cddfbcc391c16d78e9ba Mon Sep 17 00:00:00 2001 From: Dave Cornejo Date: Mon, 23 Apr 2018 08:49:45 -1000 Subject: [PATCH 44/50] include limits.h for non-Linux compatibility --- apps/backend/clixon_backend_handle.c | 1 + apps/backend/clixon_backend_transaction.c | 1 + apps/restconf/restconf_main.c | 1 + apps/restconf/restconf_methods.c | 1 + 4 files changed, 4 insertions(+) diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index de6563dc..c4568b28 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -52,6 +52,7 @@ #include #include #include +#include /* cligen */ #include diff --git a/apps/backend/clixon_backend_transaction.c b/apps/backend/clixon_backend_transaction.c index 18b5c807..173ba01c 100644 --- a/apps/backend/clixon_backend_transaction.c +++ b/apps/backend/clixon_backend_transaction.c @@ -50,6 +50,7 @@ #include #include #include +#include /* cligen */ #include diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 80e2e364..0bc98bda 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 44a26f2e..4781ea8d 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -105,6 +105,7 @@ Mapping netconf error-tag -> status code #include #include #include +#include #include #include From 602f5034b4d2f4832e4b362322a8672107fcb4b5 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 23 Apr 2018 23:09:55 +0200 Subject: [PATCH 45/50] NACM external file support. CLICON_NACM_FILE config option, if CLICON_NACM_MODE is external --- CHANGELOG.md | 1 + apps/backend/backend_client.c | 36 +- apps/backend/backend_handle.h | 4 + apps/backend/backend_main.c | 61 +++- apps/backend/clixon_backend_handle.c | 23 ++ apps/cli/Makefile.in | 2 +- example/Makefile.in | 2 +- example/example.xml | 2 + example/example_restconf.c | 7 +- lib/src/clixon_options.c | 2 +- lib/src/clixon_xml.c | 23 +- lib/src/clixon_xml_map.c | 9 +- test/test_auth.sh | 2 +- test/test_auth_ext.sh | 241 ++++++++++++++ yang/Makefile.in | 1 + yang/clixon-config@2018-02-12.yang | 9 +- yang/ietf-yang-types@2013-07-15.yang | 481 +++++++++++++++++++++++++++ 17 files changed, 867 insertions(+), 39 deletions(-) create mode 100644 test/test_auth_ext.sh create mode 100644 yang/ietf-yang-types@2013-07-15.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index e56752d8..1d3d88b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Major changes: * Experimental NACM RFC8341 Network Configuration Access Control Model. * CLICON_NACM_MODE config option, default is disabled. + * CLICON_NACM_FILE config option, if CLICON_NACM_MODE is "external" * Added username attribute to all rpc:s from frontend to backend * Added NACM backend module in example * Restructure and more generic plugin API (cli,backend,restconf,netconf). diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 1f8ac9c2..fc619640 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -904,7 +904,9 @@ nacm_match_rule(clicon_handle h, /*! Make nacm access control * @param[in] h Clicon handle + * @param[in] mode NACMmode, internal or external * @param[in] name rpc name + * @param[in] username * @param[out] cbret Cligen buffer result. Set to an error msg if retval=0. * @retval -1 Error * @retval 0 Not access and cbret set @@ -913,6 +915,7 @@ nacm_match_rule(clicon_handle h, */ static int nacm_access(clicon_handle h, + char *mode, char *name, char *username, cbuf *cbret) @@ -935,10 +938,26 @@ nacm_access(clicon_handle h, int ret; clicon_debug(1, "%s", __FUNCTION__); + /* 0. If nacm-mode is external, get NACM defintion from separet tree, + otherwise get it from internal configuration */ + if (strcmp(mode, "external")==0){ + if ((xtop = backend_nacm_list_get(h)) == NULL){ + clicon_err(OE_XML, 0, "No nacm external tree"); + goto done; + } + } + else if (strcmp(mode, "internal")==0){ + if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) + goto done; + } + else{ + clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode); + goto done; + } + /* 1. If the "enable-nacm" leaf is set to "false", then the protocol operation is permitted. (or config does not exist) */ - if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0) - goto done; + if ((xacm = xpath_first(xtop, "nacm")) == NULL) goto permit; exec_default = xml_find_body(xacm, "exec-default"); @@ -1033,7 +1052,7 @@ nacm_access(clicon_handle h, retval = 1; done: clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval); - if (xtop) + if (strcmp(mode, "internal")==0 && xtop) xml_free(xtop); if (gvec) free(gvec); @@ -1097,15 +1116,14 @@ from_client_msg(clicon_handle h, while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { name = xml_name(xe); clicon_debug(1, "%s name:%s", __FUNCTION__, name); -#if 1 /* NACM */ /* Make NACM access control if enabled as "internal"*/ nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); - if (nacm_mode && strcmp(nacm_mode,"internal") == 0) - if ((ret = nacm_access(h, name, username, cbret)) < 0) + if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){ + if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0) goto done; - if (!ret) - goto reply; -#endif + if (!ret) + goto reply; + } if (strcmp(name, "get-config") == 0){ if (from_client_get_config(h, xe, cbret) <0) goto done; diff --git a/apps/backend/backend_handle.h b/apps/backend/backend_handle.h index 8eb3e073..676cc50c 100644 --- a/apps/backend/backend_handle.h +++ b/apps/backend/backend_handle.h @@ -52,4 +52,8 @@ struct client_entry *backend_client_list(clicon_handle h); int backend_client_delete(clicon_handle h, struct client_entry *ce); +int backend_nacm_list_set(clicon_handle h, cxobj *xnacm); + +cxobj * backend_nacm_list_get(clicon_handle h); + #endif /* _BACKEND_HANDLE_H_ */ diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 346cf859..9babb24e 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -260,6 +260,59 @@ plugin_start_useroptions(clicon_handle h, return 0; } +/*! Load external NACM file + */ +static int +nacm_load_external(clicon_handle h) +{ + int retval = -1; + char *filename; /* NACM config file */ + yang_spec *yspec = NULL; + cxobj *xt = NULL; + struct stat st; + FILE *f = NULL; + int fd; + + filename = clicon_option_str(h, "CLICON_NACM_FILE"); + if (filename == NULL || strlen(filename)==0){ + clicon_err(OE_UNIX, errno, "CLICON_NACM_FILE not set in NACM external mode"); + goto done; + } + if (stat(filename, &st) < 0){ + clicon_err(OE_UNIX, errno, "%s", filename); + goto done; + } + if (!S_ISREG(st.st_mode)){ + clicon_err(OE_UNIX, 0, "%s is not a regular file", filename); + goto done; + } + if ((f = fopen(filename, "r")) == NULL) { + clicon_err(OE_UNIX, errno, "configure file: %s", filename); + return -1; + } + if ((yspec = yspec_new()) == NULL) + goto done; + if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf-acm", NULL, yspec) < 0) + goto done; + fd = fileno(f); + /* Read configfile */ + if (xml_parse_file(fd, "", yspec, &xt) < 0) + goto done; + if (xt == NULL){ + clicon_err(OE_XML, 0, "No xml tree in %s", filename); + goto done; + } + if (backend_nacm_list_set(h, xt) < 0) + goto done; + retval = 0; + done: + if (yspec) /* The clixon yang-spec is not used after this */ + yspec_free(yspec); + if (f) + fclose(f); + return retval; +} + /*! Merge xml in filename into database */ static int @@ -498,6 +551,7 @@ main(int argc, int xml_cache; int xml_pretty; char *xml_format; + char *nacm_mode; /* In the startup, logs to stderr & syslog and debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG); @@ -551,7 +605,12 @@ main(int argc, usage(argv[0], h); return -1; } - + /* External NACM file? */ + nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); + if (nacm_mode && strcmp(nacm_mode, "external") == 0) + if (nacm_load_external(h) < 0) + goto done; + /* Now run through the operational args */ opterr = 1; optind = 1; diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index c4568b28..dfb9461a 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -90,6 +90,7 @@ struct backend_handle { struct client_entry *bh_ce_list; /* The client list */ int bh_ce_nr; /* Number of clients, just increment */ struct handle_subscription *bh_subscription; /* Event subscription list */ + cxobj *bh_nacm; /* NACM external struct */ }; /*! Creates and returns a clicon config handle for other CLICON API calls @@ -106,11 +107,14 @@ backend_handle_init(void) int backend_handle_exit(clicon_handle h) { + struct backend_handle *bh = handle(h); struct client_entry *ce; /* only delete client structs, not close sockets, etc, see backend_client_rm */ while ((ce = backend_client_list(h)) != NULL) backend_client_delete(h, ce); + if (bh->bh_nacm) + xml_free(bh->bh_nacm); clicon_handle_exit(h); /* frees h and options */ return 0; } @@ -431,3 +435,22 @@ subscription_each(clicon_handle h, return hs; } +int +backend_nacm_list_set(clicon_handle h, + cxobj *xnacm) +{ + struct backend_handle *bh = handle(h); + + if (bh->bh_nacm) + xml_free(bh->bh_nacm); + bh->bh_nacm = xnacm; + return 0; +} + +cxobj * +backend_nacm_list_get(clicon_handle h) +{ + struct backend_handle *bh = handle(h); + + return bh->bh_nacm; +} diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index 1272bc83..bdea3b9e 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -103,7 +103,7 @@ distclean: clean # Put config file in etc/ install: install-lib $(APPL) install -d -m 0755 $(DESTDIR)$(bindir) - install -m 0644 -s $(APPL) $(DESTDIR)$(bindir) + install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) install -d -m 0755 $(DESTDIR)$(libdir) diff --git a/example/Makefile.in b/example/Makefile.in index b043dbef..05433e6a 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -118,7 +118,7 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NET install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/cli install -m 0644 -s $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/backend - install -m 0644 -s $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend + install -m 0644 -s $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/netconf install -m 0644 -s $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/restconf diff --git a/example/example.xml b/example/example.xml index e93b840a..337048bb 100644 --- a/example/example.xml +++ b/example/example.xml @@ -11,8 +11,10 @@ /usr/local/var/example/example.sock /usr/local/var/example/example.pidfile 1 + VARS /usr/local/var/example /usr/local/lib/xmldb/text.so 0 init + disabled
diff --git a/example/example_restconf.c b/example/example_restconf.c index f16199e1..bace08fd 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -197,7 +197,6 @@ plugin_credentials(clicon_handle h, FCGX_Request *r = (FCGX_Request *)arg; cxobj *xt = NULL; cxobj *x; - char *xbody; char *auth; char *user = NULL; char *passwd; @@ -205,11 +204,12 @@ plugin_credentials(clicon_handle h, size_t authlen; cbuf *cb = NULL; int ret; - + char *xbody; + + clicon_debug(1, "%s", __FUNCTION__); /* XXX This is a kludge to reset the user not remaining from previous */ if (clicon_username_set(h, "admin") < 0) goto done; - clicon_debug(1, "%s", __FUNCTION__); /* Check if basic_auth set, if not return OK */ if (clicon_rpc_get_config(h, "running", "authentication", &xt) < 0) goto done; @@ -249,7 +249,6 @@ plugin_credentials(clicon_handle h, cprintf(cb, "authentication/auth[user=%s]", user); if ((x = xpath_first(xt, cbuf_get(cb))) == NULL) goto fail; - passwd2 = xml_find_body(x, "password"); if (strcmp(passwd, passwd2)) goto fail; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 43611c6a..45c642e0 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -209,7 +209,7 @@ clicon_options_main(clicon_handle h) /* If file ends with .xml, assume it is new format */ if ((suffix = rindex(configfile, '.')) != NULL){ suffix++; - xml = strcmp(suffix,"xml") == 0; + xml = strcmp(suffix, "xml") == 0; } if (xml){ /* Read clixon yang file */ if ((yspec = yspec_new()) == NULL) diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 8204b04b..15e25d1a 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -164,7 +164,7 @@ xml_name_set(cxobj *xn, } if (name){ if ((xn->x_name = strdup(name)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -197,7 +197,7 @@ xml_namespace_set(cxobj *xn, } if (namespace){ if ((xn->x_namespace = strdup(namespace)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -288,7 +288,7 @@ xml_value_set(cxobj *xn, } if (val){ if ((xn->x_value = strdup(val)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -480,7 +480,7 @@ xml_child_append(cxobj *x, x->x_childvec_len++; x->x_childvec = realloc(x->x_childvec, x->x_childvec_len*sizeof(cxobj*)); if (x->x_childvec == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); return -1; } x->x_childvec[x->x_childvec_len-1] = xc; @@ -538,7 +538,7 @@ xml_new(char *name, cxobj *x; if ((x = malloc(sizeof(cxobj))) == NULL){ - clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_XML, errno, "malloc"); return NULL; } memset(x, 0, sizeof(cxobj)); @@ -1314,15 +1314,14 @@ xml_parse_file(int fd, if (endtag != NULL) endtaglen = strlen(endtag); if ((xmlbuf = malloc(xmlbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: malloc", __FUNCTION__); + clicon_err(OE_XML, errno, "malloc"); goto done; } memset(xmlbuf, 0, xmlbuflen); ptr = xmlbuf; while (1){ if ((ret = read(fd, &ch, 1)) < 0){ - clicon_err(OE_XML, errno, "%s: read: [pid:%d]\n", - __FUNCTION__, + clicon_err(OE_XML, errno, "read: [pid:%d]\n", (int)getpid()); break; } @@ -1345,7 +1344,7 @@ xml_parse_file(int fd, oldxmlbuflen = xmlbuflen; xmlbuflen *= 2; if ((xmlbuf = realloc(xmlbuf, xmlbuflen)) == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); goto done; } memset(xmlbuf+oldxmlbuflen, 0, xmlbuflen-oldxmlbuflen); @@ -1455,7 +1454,7 @@ xml_copy_one(cxobj *x0, xml_type_set(x1, xml_type(x0)); if (xml_value(x0)){ /* malloced string */ if ((x1->x_value = strdup(x0->x_value)) == NULL){ - clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); + clicon_err(OE_XML, errno, "strdup"); return -1; } } @@ -1464,7 +1463,7 @@ xml_copy_one(cxobj *x0, return -1; if (xml_cv_get(x0)){ if ((cv1 = cv_dup(xml_cv_get(x0))) == NULL){ - clicon_err(OE_XML, errno, "%s: cv_dup", __FUNCTION__); + clicon_err(OE_XML, errno, "cv_dup"); return -1; } if ((xml_cv_set(x1, cv1)) < 0) @@ -1561,7 +1560,7 @@ cxvec_append(cxobj *x, int retval = -1; if ((*vec = realloc(*vec, sizeof(cxobj *) * (*len+1))) == NULL){ - clicon_err(OE_XML, errno, "%s: realloc", __FUNCTION__); + clicon_err(OE_XML, errno, "realloc"); goto done; } (*vec)[(*len)++] = x; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 8524a413..1be9d750 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -116,26 +116,19 @@ xml2txt(FILE *f, int children=0; char *term = NULL; int retval = -1; - int encr=0; xe = NULL; /* count children */ while ((xe = xml_child_each(x, xe, -1)) != NULL) children++; if (!children){ if (xml_type(x) == CX_BODY){ - /* Kludge for escaping encrypted passwords */ - if (strcmp(xml_name(xml_parent(x)), "encrypted-password")==0) - encr++; term = xml_value(x); } else{ fprintf(f, "%*s", 4*level, ""); term = xml_name(x); } - if (encr) - fprintf(f, "\"%s\";\n", term); - else - fprintf(f, "%s;\n", term); + fprintf(f, "%s;\n", term); retval = 0; goto done; } diff --git a/test/test_auth.sh b/test/test_auth.sh index 68339ab8..163edfa8 100755 --- a/test/test_auth.sh +++ b/test/test_auth.sh @@ -15,7 +15,7 @@ cat < $cfg $cfg /usr/local/share/$APPNAME/yang - $APPNAME + $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh new file mode 100644 index 00000000..68339ab8 --- /dev/null +++ b/test/test_auth_ext.sh @@ -0,0 +1,241 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# See RFC 8321 A.2 +# But replaced ietf-netconf-monitoring with * + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang + +cat < $cfg + + $cfg + /usr/local/share/$APPNAME/yang + $APPNAME + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + false + internal + +EOF + +cat < $fyang +module $APPNAME{ + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + container authentication { + description "Example code for enabling www basic auth and some example + users"; + leaf basic_auth{ + description "Basic user / password authentication as in HTTP basic auth"; + type boolean; + default false; + } + list auth { + description "user / password entries. Valid if basic_auth=true"; + key user; + leaf user{ + description "User name"; + type string; + } + leaf password{ + description "Password"; + type string; + } + } + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +RULES=$(cat < + true + + adm1bar + + + wilmabar + + + guestbar + + + + false + deny + deny + deny + + + admin + admin + adm1 + olof + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + + + guest-acl + guest + + deny-ncm + * + * + deny + + Do not allow guests any access to any information. + + + + + limited-acl + limited + + permit-get + get + * + exec + permit + + Allow get + + + + permit-get-config + get-config + * + exec + permit + + Allow get-config + + + + + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + + + 0 +EOF +) + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "start backend -s init -f $cfg -y $fyang" +# start new backend +sudo clixon_backend -s init -f $cfg -y $fyang +if [ $? -ne 0 ]; then + err +fi + +new "kill old restconf daemon" +sudo pkill -u www-data clixon_restconf +sleep 1 +new "start restconf daemon" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang + +sleep 1 + +new "restconf DELETE whole datastore" +expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" + +new2 "auth get" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} + ' + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" + +new2 "auth get (no user: access denied)" +expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (wrong passwd: access denied)" +expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' + +new2 "auth get (access)" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +#----------------Enable NACM + +new "enable nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +new2 "admin get nacm" +expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "limited get nacm" +expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +new2 "guest get nacm" +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' + +new "admin edit nacm" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" + +new2 "limited edit nacm" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new2 "guest edit nacm" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' + +new "Kill restconf daemon" +sudo pkill -u www-data clixon_restconf + +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/yang/Makefile.in b/yang/Makefile.in index e4a8aaf9..965c0655 100644 --- a/yang/Makefile.in +++ b/yang/Makefile.in @@ -42,6 +42,7 @@ YANGSPECS = clixon-config@2018-02-12.yang YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf-acm@2018-02-14.yang YANGSPECS += ietf-inet-types@2013-07-15.yang +YANGSPECS += ietf-yang-types@2013-07-15.yang APPNAME = clixon # subdir ehere these files are installed diff --git a/yang/clixon-config@2018-02-12.yang b/yang/clixon-config@2018-02-12.yang index 84dc8622..c629a3c7 100644 --- a/yang/clixon-config@2018-02-12.yang +++ b/yang/clixon-config@2018-02-12.yang @@ -341,6 +341,13 @@ module clixon-config { leaf CLICON_NACM_MODE { type nacm_mode; default disabled; - description "RFC8341 network access configuration control model"; } + description "RFC8341 network access configuration control model + (NACM) mode: disabled, in regular (internal) config + or separate external file given by CLICON_NACM_FILE"; + } + leaf CLICON_NACM_FILE { + type string; + description "RFC8341 NACM external configuration file"; + } } } diff --git a/yang/ietf-yang-types@2013-07-15.yang b/yang/ietf-yang-types@2013-07-15.yang new file mode 100644 index 00000000..07834130 --- /dev/null +++ b/yang/ietf-yang-types@2013-07-15.yang @@ -0,0 +1,481 @@ + module ietf-yang-types { + + namespace "urn:ietf:params:xml:ns:yang:ietf-yang-types"; + prefix "yang"; + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: David Kessens + + + WG Chair: Juergen Schoenwaelder + + + Editor: Juergen Schoenwaelder + "; + + description + "This module contains a collection of generally useful derived + YANG data types. + + Copyright (c) 2013 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6991; see + the RFC itself for full legal notices."; + + revision 2013-07-15 { + description + "This revision adds the following new data types: + - yang-identifier + - hex-string + - uuid + - dotted-quad"; + reference + "RFC 6991: Common YANG Data Types"; + } + + revision 2010-09-24 { + description + "Initial revision."; + reference + "RFC 6021: Common YANG Data Types"; + } + + /*** collection of counter and gauge types ***/ + + typedef counter32 { + type uint32; + description + "The counter32 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter32 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter32 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter32. + + In the value set and its semantics, this type is equivalent + to the Counter32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter32 { + type yang:counter32; + default "0"; + description + "The zero-based-counter32 type represents a counter32 + that has the defined 'initial' value zero. + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^32-1 (4294967295 decimal), when it + wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter32 textual convention of the SMIv2."; + reference + "RFC 4502: Remote Network Monitoring Management Information + Base Version 2"; + } + + typedef counter64 { + type uint64; + description + "The counter64 type represents a non-negative integer + that monotonically increases until it reaches a + maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Counters have no defined 'initial' value, and thus, a + single value of a counter has (in general) no information + content. Discontinuities in the monotonically increasing + value normally occur at re-initialization of the + management system, and at other times as specified in the + description of a schema node using this type. If such + other times can occur, for example, the creation of + a schema node of type counter64 at times other than + re-initialization, then a corresponding schema node + should be defined, with an appropriate type, to indicate + the last discontinuity. + + The counter64 type should not be used for configuration + schema nodes. A default statement SHOULD NOT be used in + combination with the type counter64. + + In the value set and its semantics, this type is equivalent + to the Counter64 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef zero-based-counter64 { + type yang:counter64; + default "0"; + description + "The zero-based-counter64 type represents a counter64 that + has the defined 'initial' value zero. + + + + + A schema node of this type will be set to zero (0) on creation + and will thereafter increase monotonically until it reaches + a maximum value of 2^64-1 (18446744073709551615 decimal), + when it wraps around and starts increasing again from zero. + + Provided that an application discovers a new schema node + of this type within the minimum time to wrap, it can use the + 'initial' value as a delta. It is important for a management + station to be aware of this minimum time and the actual time + between polls, and to discard data if the actual time is too + long or there is no defined minimum time. + + In the value set and its semantics, this type is equivalent + to the ZeroBasedCounter64 textual convention of the SMIv2."; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + typedef gauge32 { + type uint32; + description + "The gauge32 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^32-1 (4294967295 decimal), and + the minimum value cannot be smaller than 0. The value of + a gauge32 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge32 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the Gauge32 type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef gauge64 { + type uint64; + description + "The gauge64 type represents a non-negative integer, which + may increase or decrease, but shall never exceed a maximum + value, nor fall below a minimum value. The maximum value + cannot be greater than 2^64-1 (18446744073709551615), and + the minimum value cannot be smaller than 0. The value of + a gauge64 has its maximum value whenever the information + being modeled is greater than or equal to its maximum + value, and has its minimum value whenever the information + being modeled is smaller than or equal to its minimum value. + If the information being modeled subsequently decreases + below (increases above) the maximum (minimum) value, the + gauge64 also decreases (increases). + + In the value set and its semantics, this type is equivalent + to the CounterBasedGauge64 SMIv2 textual convention defined + in RFC 2856"; + reference + "RFC 2856: Textual Conventions for Additional High Capacity + Data Types"; + } + + /*** collection of identifier-related types ***/ + + typedef object-identifier { + type string { + pattern '(([0-1](\.[1-3]?[0-9]))|(2\.(0|([1-9]\d*))))' + + '(\.(0|([1-9]\d*)))*'; + } + description + "The object-identifier type represents administratively + assigned names in a registration-hierarchical-name tree. + + Values of this type are denoted as a sequence of numerical + non-negative sub-identifier values. Each sub-identifier + value MUST NOT exceed 2^32-1 (4294967295). Sub-identifiers + are separated by single dots and without any intermediate + whitespace. + + The ASN.1 standard restricts the value space of the first + sub-identifier to 0, 1, or 2. Furthermore, the value space + of the second sub-identifier is restricted to the range + 0 to 39 if the first sub-identifier is 0 or 1. Finally, + the ASN.1 standard requires that an object identifier + has always at least two sub-identifiers. The pattern + captures these restrictions. + + Although the number of sub-identifiers is not limited, + module designers should realize that there may be + implementations that stick with the SMIv2 limit of 128 + sub-identifiers. + + This type is a superset of the SMIv2 OBJECT IDENTIFIER type + since it is not restricted to 128 sub-identifiers. Hence, + this type SHOULD NOT be used to represent the SMIv2 OBJECT + IDENTIFIER type; the object-identifier-128 type SHOULD be + used instead."; + reference + "ISO9834-1: Information technology -- Open Systems + Interconnection -- Procedures for the operation of OSI + Registration Authorities: General procedures and top + arcs of the ASN.1 Object Identifier tree"; + } + + typedef object-identifier-128 { + type object-identifier { + pattern '\d*(\.\d*){1,127}'; + } + description + "This type represents object-identifiers restricted to 128 + sub-identifiers. + + In the value set and its semantics, this type is equivalent + to the OBJECT IDENTIFIER type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef yang-identifier { + type string { + length "1..max"; + pattern '[a-zA-Z_][a-zA-Z0-9\-_.]*'; + pattern '.|..|[^xX].*|.[^mM].*|..[^lL].*'; + } + description + "A YANG identifier string as defined by the 'identifier' + rule in Section 12 of RFC 6020. An identifier must + start with an alphabetic character or an underscore + followed by an arbitrary sequence of alphabetic or + numeric characters, underscores, hyphens, or dots. + + A YANG identifier MUST NOT start with any possible + combination of the lowercase or uppercase character + sequence 'xml'."; + reference + "RFC 6020: YANG - A Data Modeling Language for the Network + Configuration Protocol (NETCONF)"; + } + + /*** collection of types related to date and time***/ + + typedef date-and-time { + type string { + pattern '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?' + + '(Z|[\+\-]\d{2}:\d{2})'; + } + description + "The date-and-time type is a profile of the ISO 8601 + standard for representation of dates and times using the + Gregorian calendar. The profile is defined by the + date-time production in Section 5.6 of RFC 3339. + + The date-and-time type is compatible with the dateTime XML + schema type with the following notable exceptions: + + (a) The date-and-time type does not allow negative years. + + (b) The date-and-time time-offset -00:00 indicates an unknown + time zone (see RFC 3339) while -00:00 and +00:00 and Z + all represent the same time zone in dateTime. + + (c) The canonical format (see below) of data-and-time values + differs from the canonical format used by the dateTime XML + schema type, which requires all times to be in UTC using + the time-offset 'Z'. + + This type is not equivalent to the DateAndTime textual + convention of the SMIv2 since RFC 3339 uses a different + separator between full-date and full-time and provides + higher resolution of time-secfrac. + + The canonical format for date-and-time values with a known time + zone uses a numeric time zone offset that is calculated using + the device's configured known offset to UTC time. A change of + the device's offset to UTC time will cause date-and-time values + to change accordingly. Such changes might happen periodically + in case a server follows automatically daylight saving time + (DST) time zone offset changes. The canonical format for + date-and-time values with an unknown time zone (usually + referring to the notion of local time) uses the time-offset + -00:00."; + reference + "RFC 3339: Date and Time on the Internet: Timestamps + RFC 2579: Textual Conventions for SMIv2 + XSD-TYPES: XML Schema Part 2: Datatypes Second Edition"; + } + + typedef timeticks { + type uint32; + description + "The timeticks type represents a non-negative integer that + represents the time, modulo 2^32 (4294967296 decimal), in + hundredths of a second between two epochs. When a schema + node is defined that uses this type, the description of + the schema node identifies both of the reference epochs. + + In the value set and its semantics, this type is equivalent + to the TimeTicks type of the SMIv2."; + reference + "RFC 2578: Structure of Management Information Version 2 + (SMIv2)"; + } + + typedef timestamp { + type yang:timeticks; + description + "The timestamp type represents the value of an associated + timeticks schema node at which a specific occurrence + happened. The specific occurrence must be defined in the + description of any schema node defined using this type. When + the specific occurrence occurred prior to the last time the + associated timeticks attribute was zero, then the timestamp + value is zero. Note that this requires all timestamp values + to be reset to zero when the value of the associated timeticks + attribute reaches 497+ days and wraps around to zero. + + The associated timeticks schema node must be specified + in the description of any schema node using this type. + + In the value set and its semantics, this type is equivalent + to the TimeStamp textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of generic address types ***/ + + typedef phys-address { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + + + + + description + "Represents media- or physical-level addresses represented + as a sequence octets, each octet represented by two hexadecimal + numbers. Octets are separated by colons. The canonical + representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the PhysAddress textual convention of the SMIv2."; + reference + "RFC 2579: Textual Conventions for SMIv2"; + } + + typedef mac-address { + type string { + pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'; + } + description + "The mac-address type represents an IEEE 802 MAC address. + The canonical representation uses lowercase characters. + + In the value set and its semantics, this type is equivalent + to the MacAddress textual convention of the SMIv2."; + reference + "IEEE 802: IEEE Standard for Local and Metropolitan Area + Networks: Overview and Architecture + RFC 2579: Textual Conventions for SMIv2"; + } + + /*** collection of XML-specific types ***/ + + typedef xpath1.0 { + type string; + description + "This type represents an XPATH 1.0 expression. + + When a schema node is defined that uses this type, the + description of the schema node MUST specify the XPath + context in which the XPath expression is evaluated."; + reference + "XPATH: XML Path Language (XPath) Version 1.0"; + } + + /*** collection of string types ***/ + + typedef hex-string { + type string { + pattern '([0-9a-fA-F]{2}(:[0-9a-fA-F]{2})*)?'; + } + description + "A hexadecimal string with octets represented as hex digits + separated by colons. The canonical representation uses + lowercase characters."; + } + + typedef uuid { + type string { + pattern '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-' + + '[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; + } + description + "A Universally Unique IDentifier in the string representation + defined in RFC 4122. The canonical representation uses + lowercase characters. + + The following is an example of a UUID in string representation: + f81d4fae-7dec-11d0-a765-00a0c91e6bf6 + "; + reference + "RFC 4122: A Universally Unique IDentifier (UUID) URN + Namespace"; + } + + typedef dotted-quad { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + } + description + "An unsigned 32-bit number expressed in the dotted-quad + notation, i.e., four octets written as decimal numbers + and separated with the '.' (full stop) character."; + } + } + From d57a6cf53cdda4a3e5da122eb34de5ce069676f0 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 24 Apr 2018 17:43:19 +0200 Subject: [PATCH 46/50] * plugin_start() callbacks added for restconf * Hard-wired users for authentication example --- CHANGELOG.md | 1 + apps/restconf/restconf_main.c | 12 +++++- example/example.yang | 21 ---------- example/example_restconf.c | 75 ++++++++++++++++++++++------------- lib/clixon/clixon_plugin.h | 4 +- lib/src/clixon_plugin.c | 12 +++++- test/test_auth.sh | 41 ++----------------- test/test_auth_ext.sh | 52 +++++++----------------- 8 files changed, 92 insertions(+), 126 deletions(-) mode change 100644 => 100755 test/test_auth_ext.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3d88b2..73006c3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h) ### Minor changes: +* plugin_start() callbacks added for restconf * Authentication * Example extended with http basic authentication for restconf * Documentation in FAQ.md diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index 0bc98bda..7ffc9260 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -503,6 +503,7 @@ main(int argc, { int retval = -1; int sock; + char *argv0 = argv[0]; FCGX_Request request; FCGX_Request *r = &request; char c; @@ -511,7 +512,8 @@ main(int argc, clicon_handle h; char *yangspec=NULL; char *dir; - + char *tmp; + /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG); /* Create handle */ @@ -575,6 +577,14 @@ main(int argc, if (yang_spec_main(h) == NULL) goto done; + /* Call start function in all plugins before we go interactive + Pass all args after the standard options to plugin_start + */ + tmp = *(argv-1); + *(argv-1) = argv0; + clixon_plugin_start(h, argc+1, argv-1); + *(argv-1) = tmp; + if ((sockpath = clicon_option_str(h, "CLICON_RESTCONF_PATH")) == NULL){ clicon_err(OE_CFG, errno, "No CLICON_RESTCONF_PATH in clixon configure file"); goto done; diff --git a/example/example.yang b/example/example.yang index 08799eae..f9159228 100644 --- a/example/example.yang +++ b/example/example.yang @@ -11,27 +11,6 @@ module example { } description "Example code that includes ietf-ip and ietf-routing"; - container authentication { - description "Example code for enabling www basic auth and some example - users"; - leaf basic_auth{ - description "Basic user / password authentication as in HTTP basic auth"; - type boolean; - default false; - } - list auth { - description "user / password entries. Valid if basic_auth=true"; - key user; - leaf user{ - description "User name"; - type string; - } - leaf password{ - description "Password"; - type string; - } - } - } rpc client-rpc { description "Example local client-side RPC that is processed by the the netconf/restconf and not sent to the backend. diff --git a/example/example_restconf.c b/example/example_restconf.c index bace08fd..fd579954 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -53,6 +53,11 @@ static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char Pad64 = '='; +/* Use http basic auth. Set by starting restonf with: + clixon_restconf ... -- -a +*/ +static int basic_auth = 0; + /* skips all whitespace anywhere. converts characters, four at a time, starting at (or after) src from base - 64 numbers into three 8 bit bytes in the target area. @@ -181,22 +186,21 @@ b64_decode(const char *src, } /*! Process a rest request that requires (cookie) "authentication" - * Note, this is loaded as dlsym fixed symbol in plugin * @param[in] h Clixon handle * @param[in] arg Argument. Here: Fastcgi request handle * @retval -1 Fatal error * @retval 0 Unauth * @retval 1 Auth - * + * @note: Three hardwired users: adm1, wilma, guest w password "bar". + * Enabled by passing -- -a to the main function */ int -plugin_credentials(clicon_handle h, - void *arg) +example_restconf_credentials(clicon_handle h, + void *arg) { int retval = -1; FCGX_Request *r = (FCGX_Request *)arg; cxobj *xt = NULL; - cxobj *x; char *auth; char *user = NULL; char *passwd; @@ -204,22 +208,9 @@ plugin_credentials(clicon_handle h, size_t authlen; cbuf *cb = NULL; int ret; - char *xbody; - - clicon_debug(1, "%s", __FUNCTION__); - /* XXX This is a kludge to reset the user not remaining from previous */ - if (clicon_username_set(h, "admin") < 0) - goto done; - /* Check if basic_auth set, if not return OK */ - if (clicon_rpc_get_config(h, "running", "authentication", &xt) < 0) - goto done; - if (clicon_username_set(h, "none") < 0) - goto done; - if ((x = xpath_first(xt, "authentication/basic_auth")) == NULL) - goto ok; - if ((xbody = xml_body(x)) == NULL) - goto ok; - if (strcmp(xbody, "true")) + + /* HTTP basic authentication not enabled, pass with user "none" */ + if (basic_auth==0) goto ok; /* At this point in the code we must use HTTP basic authentication */ if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL) @@ -242,14 +233,15 @@ plugin_credentials(clicon_handle h, goto fail; *passwd = '\0'; passwd++; - clicon_debug(1, "%s user:%s passwd:%s", __FUNCTION__, user, passwd); + clicon_debug(1, "%s http user:%s passwd:%s", __FUNCTION__, user, passwd); /* Here get auth sub-tree whjere all the users are */ if ((cb = cbuf_new()) == NULL) goto done; - cprintf(cb, "authentication/auth[user=%s]", user); - if ((x = xpath_first(xt, cbuf_get(cb))) == NULL) - goto fail; - passwd2 = xml_find_body(x, "password"); + /* Hardcoded user/passwd */ + if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 || + strcmp(user, "quest")==0){ + passwd2 = "bar"; + } if (strcmp(passwd, passwd2)) goto fail; retval = 1; @@ -287,14 +279,41 @@ restconf_client_rpc(clicon_handle h, return 0; } +/*! Start example restonf plugin. Set authentication method + * Arguments are argc/argv after -- + * Currently defined: -a enable http basic authentication + * Note hardwired users adm1, wilma and guest + */ +int +example_restconf_start(clicon_handle h, + int argc, + char **argv) +{ + char c; + + clicon_debug(1, "%s argc:%d", __FUNCTION__, argc); + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "a")) != -1){ + switch (c) { + case 'a': + basic_auth = 1; + break; + default: + break; + } + } + return 0; +} + clixon_plugin_api * clixon_plugin_init(clicon_handle h); static clixon_plugin_api api = { "example", /* name */ clixon_plugin_init, /* init */ - NULL, /* start */ + example_restconf_start,/* start */ NULL, /* exit */ - .ca_auth=plugin_credentials /* auth */ + .ca_auth=example_restconf_credentials /* auth */ }; /*! Restconf plugin initialization diff --git a/lib/clixon/clixon_plugin.h b/lib/clixon/clixon_plugin.h index ece605c7..f7aa7174 100644 --- a/lib/clixon/clixon_plugin.h +++ b/lib/clixon/clixon_plugin.h @@ -75,7 +75,9 @@ typedef int (*clicon_rpc_cb)( */ /* Called when backend started with cmd-line arguments from daemon call. - * @see plgstart_t + * Call plugin start functions with argc/argv multiple arguments. + * Typically the argc/argv are the ones appearing after "--", eg + * clicon_cli -f /etc/clicon.xml -- -a myopt */ typedef int (plgstart_t)(clicon_handle, int, char **); /* Plugin start */ diff --git a/lib/src/clixon_plugin.c b/lib/src/clixon_plugin.c index 4b6112c2..c8ce70e7 100644 --- a/lib/src/clixon_plugin.c +++ b/lib/src/clixon_plugin.c @@ -288,6 +288,16 @@ done: /*! Call plugin_start in all plugins * @param[in] h Clicon handle + * @param[in] argc + * @param[in] argv + * Call plugin start functions (if defined) with argc/argv multiple + * arguments. + * Typically the argc/argv are the ones appearing after "--", eg + * clicon_cli -f /etc/clicon.xml -- -a myopt + * In the example above argc=3 and + * argv[0]: clicon_cli + * argv[1]: -a + * argv[2]: myopt */ int clixon_plugin_start(clicon_handle h, @@ -369,7 +379,7 @@ clixon_plugin_auth(clicon_handle h, if ((authfn = cp->cp_api.ca_auth) == NULL) continue; if ((retval = authfn(h, arg)) < 0) { - clicon_debug(1, "plugin_start() failed\n"); + clicon_debug(1, "plugin_auth() failed\n"); return -1; } break; diff --git a/test/test_auth.sh b/test/test_auth.sh index 163edfa8..225a3ba7 100755 --- a/test/test_auth.sh +++ b/test/test_auth.sh @@ -36,27 +36,6 @@ module $APPNAME{ import ietf-netconf-acm { prefix nacm; } - container authentication { - description "Example code for enabling www basic auth and some example - users"; - leaf basic_auth{ - description "Basic user / password authentication as in HTTP basic auth"; - type boolean; - default false; - } - list auth { - description "user / password entries. Valid if basic_auth=true"; - key user; - leaf user{ - description "User name"; - type string; - } - leaf password{ - description "Password"; - type string; - } - } - } leaf x{ type int32; description "something to edit"; @@ -65,18 +44,6 @@ module $APPNAME{ EOF RULES=$(cat < - true - - adm1bar - - - wilmabar - - - guestbar - - false deny @@ -172,8 +139,8 @@ fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf sleep 1 -new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang +new "start restconf daemon (-a is enable basic authentication)" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a sleep 1 @@ -214,7 +181,7 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new "admin edit nacm" expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" @@ -223,7 +190,7 @@ new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf diff --git a/test/test_auth_ext.sh b/test/test_auth_ext.sh old mode 100644 new mode 100755 index 68339ab8..373fca1f --- a/test/test_auth_ext.sh +++ b/test/test_auth_ext.sh @@ -1,5 +1,6 @@ #!/bin/bash # Authentication and authorization and IETF NACM +# External NACM file # See RFC 8321 A.2 # But replaced ietf-netconf-monitoring with * @@ -10,12 +11,13 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/test.yang fyangerr=$dir/err.yang +nacmfile=$dir/nacmfile cat < $cfg $cfg /usr/local/share/$APPNAME/yang - $APPNAME + $fyang /usr/local/lib/$APPNAME/clispec /usr/local/lib/$APPNAME/restconf /usr/local/lib/$APPNAME/cli @@ -26,23 +28,21 @@ cat < $cfg /usr/local/var/$APPNAME /usr/local/lib/xmldb/text.so false - internal + external + $nacmfile EOF cat < $fyang module $APPNAME{ prefix ex; - import ietf-netconf-acm { - prefix nacm; - } container authentication { description "Example code for enabling www basic auth and some example users"; leaf basic_auth{ description "Basic user / password authentication as in HTTP basic auth"; type boolean; - default false; + default true; } list auth { description "user / password entries. Valid if basic_auth=true"; @@ -64,21 +64,9 @@ module $APPNAME{ } EOF -RULES=$(cat < - true - - adm1bar - - - wilmabar - - - guestbar - - +cat < $nacmfile - false + true deny deny deny @@ -151,12 +139,10 @@ RULES=$(cat < - 0 EOF -) # kill old backend (if any) -new "kill old backend" +new "kill old backend -zf $cfg -y $fyang" sudo clixon_backend -zf $cfg -y $fyang if [ $? -ne 0 ]; then err @@ -172,8 +158,8 @@ fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf sleep 1 -new "start restconf daemon" -sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang +new "start restconf daemon (-a is enable http basic auth)" +sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg -y $fyang -- -a sleep 1 @@ -184,11 +170,8 @@ new2 "auth get" expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data)" '{"data": null} ' -new "auth set authentication config" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "$RULES]]>]]>" "^]]>]]>$" - -new "commit it" -expecteof "$clixon_netconf -qf $cfg -y $fyang" "]]>]]>" "^]]>]]>$" +new "Set x to 0" +expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' @@ -200,11 +183,6 @@ new2 "auth get (access)" expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' -#----------------Enable NACM - -new "enable nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" - new2 "admin get nacm" expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' @@ -214,7 +192,7 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new "admin edit nacm" expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" @@ -223,7 +201,7 @@ new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new "Kill restconf daemon" sudo pkill -u www-data clixon_restconf From 680753a5e2a219e4d0dee48b380bc6647d4a516d Mon Sep 17 00:00:00 2001 From: Renato Botelho do Couto Date: Wed, 25 Apr 2018 13:56:25 -0500 Subject: [PATCH 47/50] Improve permissions and strip binaries (part 2) - Use 0755 for directories - Use 0644 for libraries, includes and shared files - Use -s (strip) parameter when installing binaries and libraries --- apps/restconf/Makefile.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 8999c847..01e25631 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -101,14 +101,14 @@ install: install-lib $(APPL) install -m 0755 -s $(APPL) $(DESTDIR)$(wwwdir) install-lib: $(MYLIB) - install -d $(DESTDIR)$(libdir) - install $(MYLIB) $(DESTDIR)$(libdir) + install -d -m 0755 $(DESTDIR)$(libdir) + install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_restconf.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_restconf.so install-include: clixon_restconf.h - install -d $(DESTDIR)$(includedir)/clixon - install -m 644 $^ $(DESTDIR)$(includedir)/clixon + install -d -m 0755 $(DESTDIR)$(includedir)/clixon + install -m 0644 $^ $(DESTDIR)$(includedir)/clixon uninstall: rm -f $(DESTDIR)$(wwwdir)/$(APPL) From 3df4a8a190220f9ca1cff2940036b8be95e87a70 Mon Sep 17 00:00:00 2001 From: Olof Hagsand Date: Fri, 27 Apr 2018 19:15:11 +0200 Subject: [PATCH 48/50] Added keyword arg to find schemanode functions --- apps/netconf/netconf_rpc.c | 2 +- apps/restconf/restconf_methods.c | 2 +- lib/clixon/clixon_yang.h | 4 ++-- lib/src/clixon_yang.c | 32 +++++++++++++++++++++++--------- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 5c8bc143..83484335 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -893,7 +893,7 @@ netconf_application_rpc(clicon_handle h, // else // cprintf(cb, "/%s", xml_name(xn)); /* XXX not accepdted by below */ /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), &yrpc) < 0) + if (yang_abs_schema_nodeid(yspec, cbuf_get(cb), Y_RPC, &yrpc) < 0) goto done; /* Check if found */ if (yrpc != NULL){ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 4781ea8d..72fe561d 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -1038,7 +1038,7 @@ api_operations_post(clicon_handle h, clicon_debug(1, "%s oppath: %s", __FUNCTION__, oppath); /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, oppath, &yrpc) < 0) + if (yang_abs_schema_nodeid(yspec, oppath, Y_RPC, &yrpc) < 0) goto done; if (yrpc == NULL){ if (netconf_operation_failed_xml(&xerr, "protocol", "yang node not found") < 0) diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 7c4d4b21..c77d64aa 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -260,9 +260,9 @@ int yang_parse(clicon_handle h, const char *yang_dir, int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn, void *arg); int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, - yang_stmt **yres); + enum rfc_6020 keyword, yang_stmt **yres); int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, - yang_stmt **yres); + enum rfc_6020 keyword, yang_stmt **yres); cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype); int ys_parse_sub(yang_stmt *ys, char *extra); int yang_mandatory(yang_stmt *ys); diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index fe119a6f..f15057a1 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -1319,7 +1319,7 @@ yang_augment_node(yang_stmt *ys, clicon_debug(1, "%s %s", __FUNCTION__, schema_nodeid); /* Find the target */ - if (yang_abs_schema_nodeid(ysp, schema_nodeid, &yss) < 0) + if (yang_abs_schema_nodeid(ysp, schema_nodeid, -1, &yss) < 0) goto done; if (yss == NULL) goto ok; @@ -1748,7 +1748,7 @@ ys_schemanode_check(yang_stmt *ys, /* fallthru */ case Y_REFINE: case Y_UNIQUE: - if (yang_desc_schema_nodeid(yp, ys->ys_argument, &yres) < 0) + if (yang_desc_schema_nodeid(yp, ys->ys_argument, -1, &yres) < 0) goto done; if (yres == NULL){ clicon_err(OE_YANG, 0, "schemanode sanity check of %d %s", @@ -1759,7 +1759,7 @@ ys_schemanode_check(yang_stmt *ys, break; case Y_DEVIATION: yspec = ys_spec(ys); - if (yang_abs_schema_nodeid(yspec, ys->ys_argument, &yres) < 0) + if (yang_abs_schema_nodeid(yspec, ys->ys_argument, -1, &yres) < 0) goto done; if (yres == NULL){ clicon_err(OE_YANG, 0, "schemanode sanity check of %s", ys->ys_argument); @@ -1902,6 +1902,7 @@ yang_apply(yang_node *yn, * @param[in] yn Yang node. Find next yang stmt and return that if match. * @param[in] vec Vector of nodeid's in a schema node identifier, eg a/b * @param[in] nvec Length of vec + * @param[in] keyword A schemode of this type, or -1 if any * @param[out] yres Result yang statement node, or NULL if not found * @retval -1 Error, with clicon_err called * @retval 0 OK @@ -1910,6 +1911,7 @@ static int schema_nodeid_vec(yang_node *yn, char **vec, int nvec, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -1940,6 +1942,8 @@ schema_nodeid_vec(yang_node *yn, ys = yn->yn_stmt[i]; if (!yang_schemanode(ys)) continue; + if (keyword != -1 && keyword != ys->ys_keyword) + continue; /* some keys dont have arguments, match on key */ if (ys->ys_keyword == Y_INPUT || ys->ys_keyword == Y_OUTPUT){ if (strcmp(nodeid, yang_key2str(ys->ys_keyword)) == 0){ @@ -1966,7 +1970,7 @@ schema_nodeid_vec(yang_node *yn, goto ok; } /* recursive call using ynext */ - if (schema_nodeid_vec(ynext, vec+1, nvec-1, yres) < 0) + if (schema_nodeid_vec(ynext, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: retval = 0; @@ -1977,16 +1981,22 @@ schema_nodeid_vec(yang_node *yn, /*! Given an absolute schema-nodeid (eg /a/b/c) find matching yang spec * @param[in] yspec Yang specification. * @param[in] schema_nodeid Absolute schema-node-id, ie /a/b + * @param[in] keyword A schemode of this type, or -1 if any * @param[out] yres Result yang statement node, or NULL if not found * @retval -1 Error, with clicon_err called * @retval 0 OK (if yres set then found, if yres=0 then not found) * Assume schema nodeid:s have prefixes, (actually the first). * @see yang_desc_schema_nodeid + * @see RFC7950 6.5 + o schema node: A node in the schema tree. One of action, container, + leaf, leaf-list, list, choice, case, rpc, input, output, + notification, anydata, and anyxml. * Used in yang: deviation, top-level augment */ int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -1996,6 +2006,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, char *id; char *prefix = NULL; yang_stmt *yprefix; + yang_stmt *ys; /* check absolute schema_nodeid */ if (schema_nodeid[0] != '/'){ @@ -2031,7 +2042,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, } } if (ymod == NULL){ /* Try with topnode */ - yang_stmt *ys; + if ((ys = yang_find_topnode(yspec, id, YC_SCHEMANODE)) == NULL){ clicon_err(OE_YANG, 0, "Module with id:%s:%s not found", prefix,id); goto done; @@ -2041,7 +2052,7 @@ yang_abs_schema_nodeid(yang_spec *yspec, goto done; } } - if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, yres) < 0) + if (schema_nodeid_vec((yang_node*)ymod, vec+1, nvec-1, keyword, yres) < 0) goto done; ok: /* yres may not be set */ retval = 0; @@ -2056,14 +2067,17 @@ yang_abs_schema_nodeid(yang_spec *yspec, /*! Given a descendant schema-nodeid (eg a/b/c) find matching yang spec * @param[in] yn Yang node * @param[in] schema_nodeid Descendant schema-node-id, ie a/b - * @retval NULL Error, with clicon_err called - * @retval yres First yang node matching schema nodeid + * @param[in] keyword A schemode of this type, or -1 if any + * @param[out] yres First yang node matching schema nodeid + * @retval 0 OK + * @retval -1 Error, with clicon_err called * @see yang_schema_nodeid * Used in yang: unique, refine, uses augment */ int yang_desc_schema_nodeid(yang_node *yn, char *schema_nodeid, + enum rfc_6020 keyword, yang_stmt **yres) { int retval = -1; @@ -2079,7 +2093,7 @@ yang_desc_schema_nodeid(yang_node *yn, } if ((vec = clicon_strsep(schema_nodeid, "/", &nvec)) == NULL) goto done; - if (schema_nodeid_vec(yn, vec, nvec, yres) < 0) + if (schema_nodeid_vec(yn, vec, nvec, keyword, yres) < 0) goto done; retval = 0; done: From 1ac57dedafcead235904eb8f83f7ba51657cdbb6 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 29 Apr 2018 18:35:06 +0200 Subject: [PATCH 49/50] Some memleaks and last minute 3.6 release changes. --- CHANGELOG.md | 48 ++++++++++++++++-------------- README.md | 35 +++++++++++++--------- README_NACM.md | 34 +++++++++++++++++++++ apps/cli/cli_show.c | 4 +-- apps/restconf/restconf_methods.c | 51 +++++++++++++++++++------------- example/README.md | 9 +++++- example/example_restconf.c | 2 +- lib/src/clixon_xml_parse.l | 10 +++---- lib/src/clixon_yang_parse.y | 6 ++-- test/README.md | 2 ++ test/lib.sh | 1 + 11 files changed, 133 insertions(+), 69 deletions(-) create mode 100644 README_NACM.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 73006c3b..cee95211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,20 +3,24 @@ ## 3.6.0 (Upcoming) ### Major changes: -* Experimental NACM RFC8341 Network Configuration Access Control Model. - * CLICON_NACM_MODE config option, default is disabled. - * CLICON_NACM_FILE config option, if CLICON_NACM_MODE is "external" - * Added username attribute to all rpc:s from frontend to backend +* Experimental NACM RFC8341 Network Configuration Access Control Model, see [NACM](README_NACM.md). + * New CLICON_NACM_MODE config option, default is disabled. + * New CLICON_NACM_FILE config option, if CLICON_NACM_MODE is "external" + * Added username attribute to all internal RPC:s from frontend to backend * Added NACM backend module in example -* Restructure and more generic plugin API (cli,backend,restconf,netconf). - * New design change `plugin_init()` to a single `clixon_plugin_init()` returning an api struct with function pointers, see example below. This means that there are no hardcoded plugin functions, except `clixon_plugin_init()`. - * Plugin RPC callback interface have been unified between backend, netconf and restconf. - * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: `backend_rpc_cb_register()` to `rpc_callback_register()` - * Backend RPC callback signature has been changed from: `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` has been changed to : `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` - * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. +* Restructure and more generic plugin API for cli, backend, restconf, and netconf. See example further down and the [example](example/README.md) + * Changed `plugin_init()` to `clixon_plugin_init()` returning an api struct with function pointers. There are no other hardcoded plugin functions. * Master plugins have been removed. Plugins are loaded alphabetically. You can ensure plugin load order by prefixing them with an ordering number, for example. + * Plugin RPC callback interface have been unified between backend, netconf and restconf. + * Backend RPC register callback function (Netconf RPC or restconf operation POST) has been changed from: + `backend_rpc_cb_register()` to `rpc_callback_register()` + * Backend RPC callback signature has been changed from: + `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + to: + `int cb(clicon_handle h, cxobj *xe, struct client_entry *ce, cbuf *cbret, void *arg)` + * Frontend netconf and restconf plugins can register callbacks as well with same API as backends. * Moved specific plugin functions from apps/ to generic functions in lib/ - * New config option CLICON_BACKEND_REGEXP to match backkend plugins (if you do not all loaded). + * New config option CLICON_BACKEND_REGEXP to match backend plugins (if you do not want to load all). * Added authentication plugin callback (ca_auth) * Added clicon_username_get() / clicon_username_set() * Removed some obscure plugin code that seem not to be used (please report if needed!) @@ -59,15 +63,14 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h) } ``` -* Added Clixon Restconf library - * Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h +* Builds and installs a new restconf library: `libclixon_restconf.so` and clixon_restconf.h * The restconf library can be included by a restconf plugin. * Example code in example/Makefile.in and example/restconf_lib.c -* Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option). +* Restconf error handling for get, put and post. (thanks Stephen Jones, Netgate) + * Available both as xml and json (set accept header). * Proper RFC 6241 Netconf error handling * New functions added in clixon_netconf_lib.[ch] * Datastore code modified for RFC 6241 - * Remaining: validate, generic restconf and netconf code ### Minor changes: @@ -81,20 +84,19 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h) * Removed username to rpc calls (added below) * README.md extended with new yang, netconf, restconf, datastore, and auth sections. * The key-value datastore is no longer supported. Use the default text datastore. -* Add username to rpc calls to prepare for authorization for backend: - clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) - clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) - +* Added username to rpc calls to prepare for authorization for backend: + * clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt) + * clicon_rpc_get(h, xpath, xt) --> clicon_rpc_get(h, xpath, username, xt) * Experimental: Added CLICON_TRANSACTION_MOD configuration option. If set, modifications in validation and commit callbacks are written back - into the datastore. + into the datastore. Requested by Stephen Jones, Netgate. * Invalid key to api_path2xml gives warning instead of error and quit. * Added restconf/operations get, see RFC8040 Sec 3.3.2: * yang_find_topnode() and api_path2xml() schemanode parameter replaced with yang_class. Replace as follows: 0 -> YC_DATANODE, 1 -> YC_SCHEMANODE * xml2json: include prefix in translation, so is translated to {"a:b" ..} -* Use instead of when save/load configuration to file. This -enables saved files to be used as datastore without any editing. Thanks Matt. +* Use `` instead of `` when save/load configuration to file. This +enables saved files to be used as datastore without any editing. Thanks Matt, Netgate. * Added Yang "extension" statement. This includes parsing unknown statements and identifying them as extensions or not. However, @@ -108,7 +110,7 @@ enables saved files to be used as datastore without any editing. Thanks Matt. * Showing syntax using CLI commands was broekn and is fixed. * Fixed issue https://github.com/clicon/clixon/issues/18 RPC response issues reported by Stephen Jones at Netgate * Fixed issue https://github.com/clicon/clixon/issues/17 special character in strings can break RPCs reported by David Cornejo at Netgate. - * This was a large rewright of XML parsing and output due to CharData not correctly encoded according to https://www.w3.org/TR/2008/REC-xml-20081126. + * This was a large rewrite of XML parsing and output due to CharData not correctly encoded according to https://www.w3.org/TR/2008/REC-xml-20081126. * Fixed three-key list entry problem (reported by jdl@netgate) * Translate xml->json \n correctly * Fix issue: https://github.com/clicon/clixon/issues/15 Replace whole config diff --git a/README.md b/README.md index 31043311..44198776 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ transaction support from a YANG specification. * [Example](example/) * [Changelog](CHANGELOG.md) * [Runtime](#runtime) - * [Clicon and Clixon project page](http://www.clicon.org) + * [Clixon project page](http://www.clicon.org) * [Tests](test/) * [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`) @@ -27,9 +27,7 @@ Background ========== Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was extended to -a framework. Most of the user projects are for embedded network and -measuring devices, but can be deployed for more general use. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, whilke clixon is a system with config-db, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. Users of clixon currently include: * [Netgate](https://www.netgate.com) @@ -51,7 +49,7 @@ A typical installation is as follows: ``` One [example application](example/README.md) is provided, a IETF IP YANG datamodel with -generated CLI and configuration interface. +generated CLI, Netconf and restconf interface. Licenses ======== @@ -63,8 +61,7 @@ See [LICENSE.md](LICENSE.md) for the license. Dependencies ============ Clixon depends on the following software packages, which need to exist on the target machine. -- [CLIgen](http://www.cligen.se) is required for building Clixon. If you need -to build and install CLIgen: +- [CLIgen](http://www.cligen.se) If you need to build and install CLIgen: ``` git clone https://github.com/olofhagsand/cligen.git cd cligen; configure; make; make install @@ -77,7 +74,9 @@ There is no yum/apt/ostree package for Clixon (please help?) Support ======= -Clixon interaction is best done posting issues, pull requests, or joining the [slack channel](https://join.slack.com/t/clixondev/shared_invite/enQtMzI3OTM4MzA3Nzk3LTA3NWM4OWYwYWMxZDhiYTNhNjRkNjQ1NWI1Zjk5M2JjMDk4MTUzMTljYTZiYmNhODkwMDI2ZTkyNWU3ZWMyN2U). +Clixon interaction is best done posting issues, pull requests, or joining the +[slack channel](https://clixondev.slack.com). +[Slack invite](https://join.slack.com/t/clixondev/shared_invite/enQtMzI3OTM4MzA3Nzk3LTA3NWM4OWYwYWMxZDhiYTNhNjRkNjQ1NWI1Zjk5M2JjMDk4MTUzMTljYTZiYmNhODkwMDI2ZTkyNWU3ZWMyN2U). Extending ========= @@ -103,7 +102,7 @@ Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020 - identity, base, identityref - list features: min/max-elements, unique -The aim is also to cover new featires in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) +The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt) Clixon has its own XML library designed for performance. @@ -111,12 +110,12 @@ Netconf ======= Clixon implements the following NETCONF proposals or standards: - [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt) -- [Using the NETCONF Configuration Protocol over Secure SHell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt) +- [Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt) - [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt) Some updates are being made to RFC 6241 and RFC 6242. -Clixon does not support the following features: +Clixon does not yet support the following netconf features: - :url capability - copy-config source config @@ -157,14 +156,22 @@ Auth Authentication is managed outside Clixon using SSH, SSL, Oauth2, etc. -For CLI, login is typically made via SSH. For netconf, SSH netconf subsystem can be used. +For CLI, login is typically made via SSH. For netconf, SSH netconf +subsystem can be used. Restconf however needs credentials. This is done by writing a credentials callback in a restconf plugin. See: * [FAQ](doc/FAQ.md#how-do-i-write-an-authentication-callback). * [Example](example/README.md) has an example how to do this with HTTP basic auth. - * It would be possible for do this for more advanced mechanisms such as Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration) + * I have done this for another project using Oauth2 or (https://github.com/CESNET/Netopeer2/tree/master/server/configuration) -There is an ongoing effort to implement authorization for Clixon according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at least a subset of the functionality. +The clients send the ID of the user using a "username" attribute with +the RPC calls to the backend. Note that the backend trusts the clients +so the clients can in principle fake a username. + +There is an ongoing effort to implement authorization for Clixon +according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at +least a subset of the functionality. See more information here: +[NACM](README_NACM.md). Runtime diff --git a/README_NACM.md b/README_NACM.md new file mode 100644 index 00000000..84e9906f --- /dev/null +++ b/README_NACM.md @@ -0,0 +1,34 @@ +# NACM + +Clixon includes an experimental NACM implementation according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). + +The support is as follows: + +* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang) +* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping. +* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted. +* The [example](example/README.md) contains a http basic auth and a NACM backend callback for state variable. +* There are two [tests](test/README.md) using internal and external NACM config +* The backend provides a limited NACM support (when enabled) decscribed below + +NACM functionality +================== + +NACM is implemented in the backend and the access checks are made at a +single point: in from_client_msg() when an internal netconf RPC has +just been received and decoded. The code is in nacm_access(). + +The functionality is as follows: +* Notification is not supported +* Groups are supported +* Rule-lists are supported +* Rules are supported as follows + * module-name: Only '*' supported + * access-operations: only '*' and 'exec' supported + * rpc-name: fully supported (eg edit-config/get-config, etc) + * action: fully supported (permit/deny) + +The tests outlines an example of three groups (taken from the RFC): admin, limited and guest: +* admin: Full access +* limited: Read access (get and get-config) +* guest: No access diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 6714413a..f4753f3c 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -94,8 +94,8 @@ expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv, - cvec *commands, - cvec *helptexts) + cvec *commands, + cvec *helptexts) { int retval = -1; char *api_path_fmt; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 4781ea8d..eb6cb00f 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -186,7 +186,8 @@ api_data_get2(clicon_handle h, cbuf *cbx = NULL; yang_spec *yspec; cxobj *xret = NULL; - cxobj *xerr = NULL; + cxobj *xerr = NULL; /* malloced */ + cxobj *xe; cxobj **xvec = NULL; size_t xlen; int i; @@ -227,8 +228,8 @@ api_data_get2(clicon_handle h, } #endif /* Check if error return XXX this needs more work */ - if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "/rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -278,6 +279,8 @@ api_data_get2(clicon_handle h, cbuf_free(cbpath); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (xvec) free(xvec); return retval; @@ -403,7 +406,8 @@ api_data_post(clicon_handle h, cxobj *xa; cxobj *xret = NULL; cxobj *xretcom = NULL; - cxobj *xerr = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -474,8 +478,8 @@ api_data_post(clicon_handle h, clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -485,10 +489,10 @@ api_data_post(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -501,6 +505,8 @@ api_data_post(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (xretcom) xml_free(xretcom); if (xtop) @@ -615,7 +621,8 @@ api_data_put(clicon_handle h, char *api_path; cxobj *xret = NULL; cxobj *xretcom = NULL; - cxobj *xerr = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xe; char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -718,8 +725,8 @@ api_data_put(clicon_handle h, clicon_debug(1, "%s xml: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xe = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -728,10 +735,10 @@ api_data_put(clicon_handle h, cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; - if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ + if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ if (clicon_rpc_discard_changes(h) < 0) goto done; - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; } @@ -744,6 +751,8 @@ api_data_put(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (xretcom) xml_free(xretcom); if (xtop) @@ -1007,16 +1016,17 @@ api_operations_post(clicon_handle h, yang_stmt *youtput; cxobj *xdata = NULL; cxobj *xret = NULL; - cxobj *xerr = NULL; + cxobj *xerr = NULL; /* malloced must be freed */ + cxobj *xer; /* non-malloced error */ cbuf *cbx = NULL; cxobj *xtop = NULL; /* xpath root */ - cxobj *xe; cxobj *xbot = NULL; yang_node *y = NULL; cxobj *xinput; cxobj *xoutput; cxobj *x; cxobj *xa; + cxobj *xe; char *username; cbuf *cbret = NULL; int ret = 0; @@ -1139,7 +1149,6 @@ api_operations_post(clicon_handle h, goto done; } xe = NULL; - while ((xe = xml_child_each(xtop, xe, CX_ELMNT)) != NULL) { /* Look for local (client-side) restconf plugins. */ if ((ret = rpc_callback_call(h, xe, cbret, r)) < 0) @@ -1148,8 +1157,8 @@ api_operations_post(clicon_handle h, if (xml_parse_string(cbuf_get(cbret), NULL, &xret) < 0) goto done; /* Local error: return it and quit */ - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xer, pretty, use_xml) < 0) goto done; goto ok; } @@ -1159,8 +1168,8 @@ api_operations_post(clicon_handle h, if (ret == 0){ /* Send to backend */ if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - if (api_return_err(h, r, xerr, pretty, use_xml) < 0) + if ((xer = xpath_first(xret, "//rpc-error")) != NULL){ + if (api_return_err(h, r, xer, pretty, use_xml) < 0) goto done; goto ok; } @@ -1221,6 +1230,8 @@ api_operations_post(clicon_handle h, xml_free(xtop); if (xret) xml_free(xret); + if (xerr) + xml_free(xerr); if (cbx) cbuf_free(cbx); if (cbret) diff --git a/example/README.md b/example/README.md index 86e70769..cafd796f 100644 --- a/example/README.md +++ b/example/README.md @@ -11,7 +11,7 @@ routing example. It contains the following files: * notification, * rpc handler * state-data handler, ie non-config data -* example_backend_secondary.c Secondary backend plugin. Plugins are loaded alphabetically. +* example_backend_nacm.c Secondary backend plugin. Plugins are loaded alphabetically. * example_restconf.c Restconf callback plugin containing an HTTP basic authentication callback * example_netconf.c Netconf callback plugin * Makefile.in Example makefile where plugins are built and installed @@ -182,8 +182,15 @@ a real example would poll or get the interface counters via a system call, as well as use the "xpath" argument to identify the requested state data. +## Authentication and NACM +The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: adm1, wilma, and guest, according to the examples in Appendix A in the RFC. +* A NACM backend plugin reporting the mandatory NACM state variables. + ## Run as docker container + +(Note not updated) ``` cd docker # look in README diff --git a/example/example_restconf.c b/example/example_restconf.c index fd579954..2b22ce16 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -204,7 +204,7 @@ example_restconf_credentials(clicon_handle h, char *auth; char *user = NULL; char *passwd; - char *passwd2; + char *passwd2 = ""; size_t authlen; cbuf *cb = NULL; int ret; diff --git a/lib/src/clixon_xml_parse.l b/lib/src/clixon_xml_parse.l index c6f1726a..e0c39884 100644 --- a/lib/src/clixon_xml_parse.l +++ b/lib/src/clixon_xml_parse.l @@ -108,11 +108,11 @@ int clixon_xml_parsewrap(void) \n { clixon_xml_parselval.string = yytext;_YA->ya_linenum++; return (CHARDATA);} . { clixon_xml_parselval.string = yytext; return CHARDATA; /*XXX:optimize*/} /* @see xml_chardata_encode */ -"amp; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = strdup("&"); return CHARDATA;} -"lt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = strdup("<"); return CHARDATA;} -"gt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = strdup(">"); return CHARDATA;} -"apos; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = strdup("'"); return CHARDATA;} -"aquot; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = strdup("'"); return CHARDATA;} +"amp; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "&"; return CHARDATA;} +"lt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "<"; return CHARDATA;} +"gt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = ">"; return CHARDATA;} +"apos; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "'"; return CHARDATA;} +"aquot; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "'"; return CHARDATA;} "-->" { BEGIN(START); return ECOMMENT; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 7cf0d253..eb48253f 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -1042,8 +1042,8 @@ extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argu | { clicon_debug(2,"extension-substmt -> "); } ; -argument_stmt : K_ARGUMENT id_arg_str ';' - | K_ARGUMENT id_arg_str '{' '}' +argument_stmt : K_ARGUMENT id_arg_str ';' { free($2); } + | K_ARGUMENT id_arg_str '{' '}' { free($2); } ; /* Feature */ @@ -1403,7 +1403,7 @@ qstring : DQ ustring DQ { $$=$2; clicon_debug(2,"string-> \" ustring \"" ustring : ustring CHAR { int len = strlen($1); - $$ = realloc($1, len+strlen($2) + 1); + $$ = realloc($1, len+strlen($2) + 1); sprintf($$+len, "%s", $2); free($2); } diff --git a/test/README.md b/test/README.md index 7598af48..08c92f1a 100644 --- a/test/README.md +++ b/test/README.md @@ -4,6 +4,8 @@ This directory contains testing code for clixon and the example routing application. Assumes setup of http daemon as describe under apps/restonf - clixon A top-level script clones clixon in /tmp and starts all.sh. You can copy this file (review it first) and place as cron script - all.sh Run through all tests named 'test*.sh' in this directory. Therefore, if you place a test in this directory matching 'test*.sh' it will be run automatically. +- test_auth.sh Auth tests using internal NACM +- test_auth_ext.sh Auth tests using external NACM - test_cli.sh CLI tests - test_netconf.sh Netconf tests - test_restconf.sh Restconf tests diff --git a/test/lib.sh b/test/lib.sh index 6a6f0e0b..061730f6 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -18,6 +18,7 @@ clixon_netconf=clixon_netconf #sudo su -c "/www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data #sudo su -c "valgrind --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data +#clixon_backend="valgrind --leak-check=full --show-leak-kinds=all clixon_backend" clixon_backend=clixon_backend dir=/var/tmp/$0 From 7532fdde77a91c31d0a59a08697adcef15f16626 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 30 Apr 2018 12:06:19 +0200 Subject: [PATCH 50/50] * INSTALLFLAGS added with default value -s(strip). * For debug do: CFLAGS=-g INSTALLFLAGS= ./configure --- CHANGELOG.md | 2 ++ Makefile.in | 2 +- develop.md => README_DEVELOP.md | 25 ++++++++++++++++--------- README_NACM.md | 8 ++++---- apps/backend/Makefile.in | 5 +++-- apps/cli/Makefile.in | 5 +++-- apps/netconf/Makefile.in | 5 +++-- apps/restconf/Makefile.in | 5 +++-- configure | 11 +++++++---- configure.ac | 11 ++++++----- datastore/Makefile.in | 5 +++-- datastore/keyvalue/Makefile.in | 3 ++- datastore/text/Makefile.in | 3 ++- example/Makefile.in | 9 +++++---- lib/src/Makefile.in | 5 +++-- 15 files changed, 63 insertions(+), 41 deletions(-) rename develop.md => README_DEVELOP.md (85%) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee95211..65020e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ clixon_plugin_api *clixon_plugin_init(clicon_handle h) ### Minor changes: +* INSTALLFLAGS added with default value -s(strip). + * For debug do: CFLAGS=-g INSTALLFLAGS= ./configure * plugin_start() callbacks added for restconf * Authentication * Example extended with http basic authentication for restconf diff --git a/Makefile.in b/Makefile.in index 3466457d..04c72ab5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -47,7 +47,7 @@ CC = @CC@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ -#INSTALL = @INSTALL@ +INSTALL = @INSTALL@ INCLUDES = -I. -I@srcdir@ @INCLUDES@ SHELL = /bin/sh diff --git a/develop.md b/README_DEVELOP.md similarity index 85% rename from develop.md rename to README_DEVELOP.md index 4ff98354..350d3636 100644 --- a/develop.md +++ b/README_DEVELOP.md @@ -1,11 +1,12 @@ -# README for developers Clixon developers +# README for Clixon developers -1. How to document the code -2. How to work in git (branching) -3. How the meta-configure stuff works -4. How to debug + * [Code documentation](#documentation) + * [How to work in git (branching)](#branching) + * [How the meta-configure stuff works](#meta-configure) + * [How to debug](#debug) -## How to document the code +## Documentation +How to document the code ``` /*! This is a small comment on one line @@ -26,7 +27,8 @@ */ ``` -## How to work in git (branching) +## Branching +How to work in git (branching) Basically follows: http://nvie.com/posts/a-successful-git-branching-model/ only somewhat simplified: @@ -49,11 +51,16 @@ configure.ac --. Makefile.in ---' `-> Makefile ---' ``` -## How to debug +## Debug +How to debug + +### Configure in debug mode +``` + CFLAGS="-g -Wall" INSTALLFLAGS="" ./configure +``` ### Make your own simplified yang and configuration file. ``` - cat < /tmp/my.yang module mymodule{ container x { diff --git a/README_NACM.md b/README_NACM.md index 84e9906f..7ec76ee3 100644 --- a/README_NACM.md +++ b/README_NACM.md @@ -7,15 +7,15 @@ The support is as follows: * There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang) * If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping. * If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted. -* The [example](example/README.md) contains a http basic auth and a NACM backend callback for state variable. +* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables. * There are two [tests](test/README.md) using internal and external NACM config -* The backend provides a limited NACM support (when enabled) decscribed below +* The backend provides a limited NACM support (when enabled) described below NACM functionality ================== -NACM is implemented in the backend and the access checks are made at a -single point: in from_client_msg() when an internal netconf RPC has +NACM is implemented in the backend and a single access check is made +in from_client_msg() when an internal netconf RPC has just been received and decoded. The code is in nacm_access(). The functionality is as follows: diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index d5ce35b2..4d57dfc8 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -36,6 +36,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -99,11 +100,11 @@ distclean: clean # Put config file in etc/ install: install-lib $(APPL) install -d -m 0755 $(DESTDIR)$(sbindir) - install -m 0755 -s $(APPL) $(DESTDIR)$(sbindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(sbindir) install-lib: $(MYLIB) install -d -m 0755 $(DESTDIR)$(libdir) - install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_config.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_config.so install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/backend diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index bdea3b9e..a55918d9 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -36,6 +36,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -103,11 +104,11 @@ distclean: clean # Put config file in etc/ install: install-lib $(APPL) install -d -m 0755 $(DESTDIR)$(bindir) - install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) install -d -m 0755 $(DESTDIR)$(libdir) - install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_cli.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_cli.so install -d -m 0755 $(DESTDIR)$(libdir)/clixon/plugins/cli diff --git a/apps/netconf/Makefile.in b/apps/netconf/Makefile.in index b9e0b1a0..81d86577 100644 --- a/apps/netconf/Makefile.in +++ b/apps/netconf/Makefile.in @@ -35,6 +35,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -101,11 +102,11 @@ distclean: clean # Put config file in etc/ install: install-lib $(APPL) install -d -m 0755 $(DESTDIR)$(bindir) - install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) install-lib: $(MYLIB) install -d -m 0755 $(DESTDIR)$(libdir) - install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_netconf.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_netconf.so diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 01e25631..d54f1255 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -35,6 +35,7 @@ srcdir = @srcdir@ top_srcdir = @top_srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ prefix = @prefix@ @@ -98,11 +99,11 @@ distclean: clean # Put config file in etc/ install: install-lib $(APPL) install -d -m 0755 $(DESTDIR)$(wwwdir) - install -m 0755 -s $(APPL) $(DESTDIR)$(wwwdir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(wwwdir) install-lib: $(MYLIB) install -d -m 0755 $(DESTDIR)$(libdir) - install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_restconf.so.2 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_restconf.so diff --git a/configure b/configure index aaf3caff..3e4fb4b6 100755 --- a/configure +++ b/configure @@ -639,6 +639,7 @@ EXE_SUFFIX SH_SUFFIX AR_SUFFIX OBJ_SUFFIX +INSTALLFLAGS CPPFLAGS INCLUDES LDFLAGS @@ -2141,8 +2142,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -# Default CFLAGS unless set by environment. -: ${CFLAGS="-O2"} +# Default CFLAGS unless set by environment +: ${CFLAGS="-O2 -Wall"} +: ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="6" @@ -2343,6 +2345,7 @@ test -n "$target_alias" && + # If yes, compile apps/restconf # ac_ext=c @@ -3277,12 +3280,12 @@ CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" { $as_echo "$as_me:${as_lineno-$LINENO}: result: compiler is $CC" >&5 $as_echo "compiler is $CC" >&6; } -CFLAGS="${CFLAGS} -Wall" - { $as_echo "$as_me:${as_lineno-$LINENO}: result: CPPFLAGS is $CPPFLAGS" >&5 $as_echo "CPPFLAGS is $CPPFLAGS" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: CFLAGS is $CFLAGS" >&5 $as_echo "CFLAGS is $CFLAGS" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: INSTALLFLAGS is $INSTALLFLAGS" >&5 +$as_echo "INSTALLFLAGS is $INSTALLFLAGS" >&6; } for ac_prog in 'bison -y' byacc do diff --git a/configure.ac b/configure.ac index 22614971..2990f5d6 100644 --- a/configure.ac +++ b/configure.ac @@ -38,8 +38,9 @@ AC_INIT(lib/clixon/clixon.h.in) -# Default CFLAGS unless set by environment. -: ${CFLAGS="-O2"} +# Default CFLAGS unless set by environment +: ${CFLAGS="-O2 -Wall"} +: ${INSTALLFLAGS="-s"} CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="6" @@ -77,6 +78,7 @@ AC_SUBST(CFLAGS) AC_SUBST(LDFLAGS) AC_SUBST(INCLUDES) AC_SUBST(CPPFLAGS) +AC_SUBST(INSTALLFLAGS) AC_SUBST(LIBS) AC_SUBST(OBJ_SUFFIX) AC_SUBST(AR_SUFFIX) @@ -93,10 +95,9 @@ CPPFLAGS="-DHAVE_CONFIG_H ${CPPFLAGS}" AC_MSG_RESULT(compiler is $CC) -CFLAGS="${CFLAGS} -Wall" - AC_MSG_RESULT(CPPFLAGS is $CPPFLAGS) -AC_MSG_RESULT(CFLAGS is $CFLAGS) +AC_MSG_RESULT(CFLAGS is $CFLAGS) +AC_MSG_RESULT(INSTALLFLAGS is $INSTALLFLAGS) AC_PROG_YACC AC_PROG_LEX diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 2e94305e..16beab79 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -44,7 +44,8 @@ libexecdir = @libexecdir@ localstatedir = @localstatedir@ sysconfdir = @sysconfdir@ CC = @CC@ -CFLAGS = @CFLAGS@ +CFLAGS = @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ with_restconf = @with_restconf@ @@ -102,7 +103,7 @@ install-include: install: $(APPL) install -d -m 0755 $(DESTDIR)$(bindir) - install -m 0755 -s $(APPL) $(DESTDIR)$(bindir) + install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(bindir) for i in $(SUBDIRS); \ do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in index 57d091ff..19aa6fe6 100644 --- a/datastore/keyvalue/Makefile.in +++ b/datastore/keyvalue/Makefile.in @@ -47,6 +47,7 @@ sysconfdir = @sysconfdir@ VPATH = @srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ DATASTORE = keyvalue @@ -81,7 +82,7 @@ distclean: clean install: $(PLUGIN) install -d -m 0755 $(DESTDIR)$(libdir)/xmldb - install -m 0644 -s $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index cd48ed50..cdc2adb5 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -47,6 +47,7 @@ HOST_VENDOR = @host_vendor@ VPATH = @srcdir@ CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ DATASTORE = text @@ -85,7 +86,7 @@ distclean: clean install: $(PLUGIN) install -d -m 0755 $(DESTDIR)$(libdir)/xmldb - install -m 0644 -s $(PLUGIN) $(DESTDIR)$(libdir)/xmldb + install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb install-include: diff --git a/example/Makefile.in b/example/Makefile.in index 05433e6a..0be6a8aa 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -41,6 +41,7 @@ datarootdir = @datarootdir@ APPNAME = example CC = @CC@ CFLAGS = @CFLAGS@ -rdynamic -fPIC +INSTALLFLAGS = @INSTALLFLAGS@ INCLUDES = -I$(includedir) @INCLUDES@ @@ -116,13 +117,13 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NET install -d -m 0755 $(DESTDIR)$(clixon_DBSPECDIR)/yang install -m 0644 $(YANGSPECS) $(DESTDIR)$(clixon_DBSPECDIR)/yang install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/cli - install -m 0644 -s $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli + install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/cli install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/backend - install -m 0644 -s $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend + install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(BE2_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/netconf - install -m 0644 -s $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf + install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/restconf - install -m 0644 $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf + install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf install -d -m 0755 $(DESTDIR)$(clixon_LIBDIR)/clispec install -m 0644 $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec install -d -m 0755 $(DESTDIR)$(clixon_LOCALSTATEDIR) diff --git a/lib/src/Makefile.in b/lib/src/Makefile.in index 8d714200..b26efee5 100644 --- a/lib/src/Makefile.in +++ b/lib/src/Makefile.in @@ -51,7 +51,8 @@ CLIXON_MINOR = @CLIXON_VERSION_MINOR@ VPATH = @srcdir@ CC = @CC@ -CFLAGS = -fPIC @CFLAGS@ +CFLAGS = -fPIC @CFLAGS@ +INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ @@ -182,7 +183,7 @@ install-include: install-lib: $(MYLIB) install -m 0755 -d $(DESTDIR)$(libdir) - install -m 0644 -s $(MYLIB) $(DESTDIR)$(libdir) + install -m 0644 $(INSTALLFLAGS) $(MYLIB) $(DESTDIR)$(libdir) ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon.so.3 ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon.so