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"; } } }