* 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
This commit is contained in:
Olof hagsand 2018-04-19 22:44:15 +02:00
parent 04a3f4db1b
commit 7650803475
32 changed files with 908 additions and 274 deletions

View file

@ -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 | <get>, <get-config> | read |
| GET | all | <get>, <get-config> | read |
| POST | datastore, data | <edit-config> | create |
| POST | operation | specified operation | execute |
| PUT | data | <edit-config> | create, update |
| PUT | datastore | <copy-config> | update |
| PATCH | data, datastore | <edit-config> | update |
| DELETE | data | <edit-config> | 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 <close-session>
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<rlistlen; i++){
xrlist = rlistvec[i];
/* Loop through user's group to find match in this rule-list */
for (j=0; j<glen; j++){
char *gname;
gname = xml_find_body(gvec[j], "name");
if (xpath_first(xrlist,".[group=%s]", gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */
continue;
/* 7. For each rule-list entry found, process all rules, in order,
until a rule that matches the requested access operation is
found.
*/
if (xpath_vec(xrlist, "rule", &rvec, &rlen) < 0)
goto done;
for (j=0; j<rlen; j++){
xrule = rvec[j];
/* -1 error, 0 deny, 1 permit, 2 continue */
if ((ret = nacm_match_rule(h, name, xrule, cbret)) < 0)
goto done;
switch(ret){
case 0: /* deny */
goto deny;
break;
case 1: /* permit */
goto permit;
break;
case 2: /* no match, continue */
break;
}
}
}
step10:
/* 10. If the requested protocol operation is defined in a YANG module
advertised in the server capabilities and the "rpc" statement
contains a "nacm:default-deny-all" statement, then the protocol
operation is denied. */
/* 11. If the requested protocol operation is the NETCONF
<kill-session> or <delete-config>, 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;

View file

@ -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;

View file

@ -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)

View file

@ -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);

View file

@ -53,6 +53,7 @@
#include <sys/stat.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <pwd.h>
#include <assert.h>
#include <libgen.h>
@ -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 */
/*

View file

@ -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

View file

@ -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);

View file

@ -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: <db> <xmlkeyfmt>",
__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)

View file

@ -55,6 +55,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <pwd.h>
#include <netinet/in.h>
#include <libgen.h>
@ -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 */

View file

@ -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, "<h1>Not Found</h1>\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);

View file

@ -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");

View file

@ -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, "<rpc><edit-config><target><candidate /></target>");
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
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, "<rpc><commit/></rpc>", &xretcom, NULL) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<commit/></rpc>");
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, "<rpc><edit-config><target><candidate /></target>");
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
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, "<rpc><commit/></rpc>", &xretcom, NULL) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<commit/></rpc>");
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, "<rpc><edit-config><target><candidate /></target>");
/* For internal XML protocol: add username attribute for access control
*/
username = clicon_username_get(h);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
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, "<rpc><commit/></rpc>", &xretcom, NULL) < 0)
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<commit/></rpc>");
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: