* Extended the Restconf implementation with a limited www-data
* This is an experimental implementation
* Added `www-data` feature and two new config options to clixon-config.yang:
* `CLICON_WWW_DATA_PATH`
* `CLICON_WWW_DATA_ROOT`
* The limited implemtation is as follows:
* path: Local files within `CLICON_WWW_DATA_ROOT`
* operation GET, HEAD, or OPTIONS
* query parameters not supported
* indata should be NULL (no write operations)
* Limited media: text/html, JavaScript, image, and css
* Authentication as restconf
Generic changes:
* Uniform path selection across fcgi, native http/1 + http/2
This commit is contained in:
parent
e1bec5f6dd
commit
76213057b6
16 changed files with 572 additions and 41 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
|
@ -39,14 +39,26 @@ Expected: May 2022
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
|
* Extended the Restconf implementation with a limited www-data
|
||||||
|
* Added two new config options to clixon-config.yang:
|
||||||
|
* `CLICON_WWW_DATA_PATH`
|
||||||
|
* `CLICON_WWW_DATA_ROOT`
|
||||||
|
* The limited implemtation is as follows:
|
||||||
|
* path: Local files within `CLICON_WWW_DATA_ROOT`
|
||||||
|
* operation GET, HEAD, or OPTIONS
|
||||||
|
* query parameters not supported
|
||||||
|
5. indata should be NULL (no write operations)
|
||||||
|
6. Limited media: text/html, JavaScript, image, and css
|
||||||
|
7. Authentication as restconf
|
||||||
|
Generic changes:
|
||||||
|
* Uniform path selection across fcgi, native http/1 + http/2
|
||||||
|
|
||||||
* Implementation of "chunked framing" according to RFC6242 for Netconf 1.1.
|
* Implementation of "chunked framing" according to RFC6242 for Netconf 1.1.
|
||||||
* First hello is 1.0 EOM framing, then successing rpc is chunked framing
|
* First hello is 1.0 EOM framing, then successing rpc is chunked framing
|
||||||
* See
|
* See
|
||||||
* [Netconf framing](https://github.com/clicon/clixon/issues/50), and
|
* [Netconf framing](https://github.com/clicon/clixon/issues/50), and
|
||||||
* [Clixon does not switch to chunked framing after NETCONF 1.1 is negotiated](https://github.com/clicon/clixon/issues/314)
|
* [Clixon does not switch to chunked framing after NETCONF 1.1 is negotiated](https://github.com/clicon/clixon/issues/314)
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
### API changes on existing protocol/config features
|
### API changes on existing protocol/config features
|
||||||
|
|
||||||
Users may have to change how they access the system
|
Users may have to change how they access the system
|
||||||
|
|
@ -56,6 +68,10 @@ Users may have to change how they access the system
|
||||||
* New `clixon-config@2022-03-21.yang` revision
|
* New `clixon-config@2022-03-21.yang` revision
|
||||||
* Added option:
|
* Added option:
|
||||||
* `CLICON_NETCONF_BASE_CAPABILITY`
|
* `CLICON_NETCONF_BASE_CAPABILITY`
|
||||||
|
* `CLICON_WWW_DATA_PATH`
|
||||||
|
* `CLICON_WWW_DATA_ROOT`
|
||||||
|
* Added feature: `www-data`
|
||||||
|
|
||||||
* Netconf data-not-unique info changed to return schema nodes instead of XML for RFC7950 compliance
|
* Netconf data-not-unique info changed to return schema nodes instead of XML for RFC7950 compliance
|
||||||
* CLI reconnects to backend if backend restarts with a warning
|
* CLI reconnects to backend if backend restarts with a warning
|
||||||
* Note that edits to the candidate database or locks will be lost
|
* Note that edits to the candidate database or locks will be lost
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ APPSRC += restconf_methods_post.c
|
||||||
APPSRC += restconf_methods_get.c
|
APPSRC += restconf_methods_get.c
|
||||||
APPSRC += restconf_methods_patch.c
|
APPSRC += restconf_methods_patch.c
|
||||||
APPSRC += restconf_root.c
|
APPSRC += restconf_root.c
|
||||||
|
APPSRC += clixon_www_data.c
|
||||||
APPSRC += restconf_main_$(with_restconf).c
|
APPSRC += restconf_main_$(with_restconf).c
|
||||||
ifeq ($(with_restconf),native)
|
ifeq ($(with_restconf),native)
|
||||||
APPSRC += restconf_http1.c
|
APPSRC += restconf_http1.c
|
||||||
|
|
|
||||||
342
apps/restconf/clixon_www_data.c
Normal file
342
apps/restconf/clixon_www_data.c
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright (C) 2009-2019 Olof Hagsand
|
||||||
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||||
|
|
||||||
|
This file is part of CLIXON.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
Alternatively, the contents of this file may be used under the terms of
|
||||||
|
the GNU General Public License Version 3 or later (the "GPL"),
|
||||||
|
in which case the provisions of the GPL are applicable instead
|
||||||
|
of those above. If you wish to allow use of your version of this file only
|
||||||
|
under the terms of the GPL, and not to allow others to
|
||||||
|
use your version of this file under the terms of Apache License version 2,
|
||||||
|
indicate your decision by deleting the provisions above and replace them with
|
||||||
|
the notice and other provisions required by the GPL. If you do not delete
|
||||||
|
the provisions above, a recipient may use your version of this file under
|
||||||
|
the terms of any one of the Apache License version 2 or the GPL.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*
|
||||||
|
* Limited www data handler embedded in restconf code
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "clixon_config.h" /* generated by config & autoconf */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <sys/stat.h> /* chmod */
|
||||||
|
|
||||||
|
/* cligen */
|
||||||
|
#include <cligen/cligen.h>
|
||||||
|
|
||||||
|
/* clicon */
|
||||||
|
#include <clixon/clixon.h>
|
||||||
|
|
||||||
|
/* restconf */
|
||||||
|
#include "restconf_lib.h"
|
||||||
|
#include "restconf_handle.h"
|
||||||
|
#include "restconf_api.h"
|
||||||
|
#include "restconf_err.h"
|
||||||
|
#include "clixon_www_data.h"
|
||||||
|
|
||||||
|
/*! Check if uri path denotes a data path
|
||||||
|
*
|
||||||
|
* @param[out] data Pointer to string where data starts if retval = 1
|
||||||
|
* @retval 0 No, not a data path
|
||||||
|
* @retval 1 Yes, a data path and "data" points to www-data if given
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
api_path_is_data(clicon_handle h,
|
||||||
|
char **data)
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
char *www_data_path;
|
||||||
|
|
||||||
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
|
return 0;
|
||||||
|
if ((www_data_path = clicon_option_str(h, "CLICON_WWW_DATA_PATH")) == NULL)
|
||||||
|
return 0;
|
||||||
|
if (strlen(path) < 1 + strlen(www_data_path)) /* "/" + www_data_path */
|
||||||
|
return 0;
|
||||||
|
if (path[0] != '/')
|
||||||
|
return 0;
|
||||||
|
if (strncmp(path+1, www_data_path, strlen(www_data_path)) != 0)
|
||||||
|
return 0;
|
||||||
|
if (data)
|
||||||
|
*data = path + 1 + strlen(www_data_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Generic restconf error function on get/head request
|
||||||
|
* @param[in] h Clixon handle
|
||||||
|
* @param[in] req Generic http handle
|
||||||
|
* @param[in] code Error code
|
||||||
|
* @see api_return_err
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
api_www_data_err(clicon_handle h,
|
||||||
|
void *req,
|
||||||
|
int code)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
cbuf *cb = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if ((cb = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
|
||||||
|
goto done;
|
||||||
|
cprintf(cb, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n");
|
||||||
|
cprintf(cb, "<html><head>\r\n");
|
||||||
|
cprintf(cb, "<title>%d %s</title>\r\n", code, restconf_code2reason(code));
|
||||||
|
cprintf(cb, "</head><body>\r\n");
|
||||||
|
cprintf(cb, "<h1>%s</h1>\r\n", restconf_code2reason(code));
|
||||||
|
#if 0 /* Cant find this sentence in any RFC, is it ad-hoc? */
|
||||||
|
cprintf(cb, "<p>The requested URL was not found on this server.</p>\r\n");
|
||||||
|
#endif
|
||||||
|
cprintf(cb, "</body></html>\r\n");
|
||||||
|
if (restconf_reply_send(req, code, cb, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
cb = NULL;
|
||||||
|
// ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||||
|
if (cb)
|
||||||
|
cbuf_free(cb);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Read file data request
|
||||||
|
* @param[in] h Clicon handle
|
||||||
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||||
|
* @param[in] pathname With stripped prefix (eg /data), ultimately a filename
|
||||||
|
* @note: primitive file handling, just check if file exists and read it all
|
||||||
|
* XXX 1: Buffer copying once too many, see #if 0 below
|
||||||
|
* XXX 2: Generic file system below CLICON_WWW_DATA_ROOT, no checks for links or ..
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
api_www_data_file(clicon_handle h,
|
||||||
|
void *req,
|
||||||
|
char *pathname,
|
||||||
|
int head)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
cbuf *cbfile = NULL;
|
||||||
|
char *filename;
|
||||||
|
struct stat fstat;
|
||||||
|
cbuf *cbdata = NULL;
|
||||||
|
FILE *f = NULL;
|
||||||
|
long fsize;
|
||||||
|
size_t sz;
|
||||||
|
char *www_data_root = NULL;
|
||||||
|
|
||||||
|
if ((cbfile = cbuf_new()) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((www_data_root = clicon_option_str(h, "CLICON_WWW_DATA_ROOT")) == NULL){
|
||||||
|
clicon_err(OE_RESTCONF, ENOENT, "CLICON_WWW_DATA_ROOT missing");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* Need pathname santitization: no .. or ~, just a directory structure.
|
||||||
|
*/
|
||||||
|
cprintf(cbfile, "%s", www_data_root);
|
||||||
|
if (pathname)
|
||||||
|
cprintf(cbfile, "/%s", pathname);
|
||||||
|
filename = cbuf_get(cbfile);
|
||||||
|
if (stat(filename, &fstat) < 0){
|
||||||
|
if (api_www_data_err(h, req, 404) < 0) /* not found */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
if ((f = fopen(filename, "rb")) == NULL){
|
||||||
|
if (api_www_data_err(h, req, 403) < 0) /* Forbidden or 500? */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
fsize = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET); /* same as rewind(f); */
|
||||||
|
if ((cbdata = cbuf_new_alloc(fsize+1)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_new_alloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
#if 0 /* Direct read but cannot set cb_len via API */
|
||||||
|
fread(cbuf_get(cbdata), fsize, 1, f);
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
char *str;
|
||||||
|
if ((str = malloc(fsize + 1)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((sz = fread(str, fsize, 1, f)) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "fread");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (sz != 1){
|
||||||
|
clicon_log(LOG_NOTICE, "%s: file read %s", __FUNCTION__, filename);
|
||||||
|
// XXX error handling: file read
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
str[fsize] = 0;
|
||||||
|
if (cbuf_append_str(cbdata, str) < 0){
|
||||||
|
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (restconf_reply_header(req, "Content-Type", "%s", "text/html") < 0)
|
||||||
|
goto done;
|
||||||
|
if (restconf_reply_send(req, 200, cbdata, head) < 0)
|
||||||
|
goto done;
|
||||||
|
cbdata = NULL; /* consumed by reply-send */
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
if (f)
|
||||||
|
fclose(f);
|
||||||
|
if (cbfile)
|
||||||
|
cbuf_free(cbfile);
|
||||||
|
if (cbdata)
|
||||||
|
cbuf_free(cbdata);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! Gete data request
|
||||||
|
*
|
||||||
|
* This implementation is constrained as follows:
|
||||||
|
* 1. Enable as part of restconf and set feature www-data and CLICON_WWW_DATA_PATH
|
||||||
|
* 2. path: Local files within CLICON_WWW_DATA_ROOT
|
||||||
|
* 3. operations: GET, HEAD, OPTIONS
|
||||||
|
* 4. query parameters not supported
|
||||||
|
* 5. indata should be NULL (no write operations)
|
||||||
|
* 6. Limited media: text/html, JavaScript, image, and css
|
||||||
|
* 7. Authentication as restconf
|
||||||
|
* @param[in] h Clicon handle
|
||||||
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||||
|
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||||
|
* @param[in] pathname With stripped prefix (eg /data), ultimately a filename
|
||||||
|
* Need to enable clixon-restconf.yang www-data feature
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
api_www_data(clicon_handle h,
|
||||||
|
void *req,
|
||||||
|
cvec *qvec)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *request_method = NULL;
|
||||||
|
char *media_str = NULL;
|
||||||
|
int head = 0;
|
||||||
|
int options = 0;
|
||||||
|
int ret;
|
||||||
|
cbuf *indata = NULL;
|
||||||
|
char *pathname = NULL;
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
if (req == NULL){
|
||||||
|
errno = EINVAL;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
/* 1. path: with stripped prefix, ultimately: dir/filename
|
||||||
|
*/
|
||||||
|
if (!api_path_is_data(h, &pathname)){
|
||||||
|
if (api_www_data_err(h, req, 404) < 0) /* not found */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* 2. operation GET or HEAD */
|
||||||
|
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
||||||
|
if (strcmp(request_method, "GET") == 0){
|
||||||
|
}
|
||||||
|
else if (strcmp(request_method, "HEAD") == 0){
|
||||||
|
head = 1;
|
||||||
|
}
|
||||||
|
else if (strcmp(request_method, "OPTIONS") == 0){
|
||||||
|
options = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (api_www_data_err(h, req, 405) < 0) /* method not allowed */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* 3. query parameters not accepted */
|
||||||
|
if (qvec != NULL){
|
||||||
|
if (api_www_data_err(h, req, 400) < 0) /* bad request */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* 4. indata should be NULL (no write operations) */
|
||||||
|
if ((indata = restconf_get_indata(req)) == NULL) {
|
||||||
|
clicon_err(OE_RESTCONF, ENOENT, "Unexpected no input cbuf");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (cbuf_len(indata)){
|
||||||
|
if (api_www_data_err(h, req, 400) < 0) /* bad request */
|
||||||
|
goto done;
|
||||||
|
goto ok;
|
||||||
|
}
|
||||||
|
/* 5. Accepted media_out: should check text/html, JavaScript, image, and css
|
||||||
|
*/
|
||||||
|
if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
|
||||||
|
}
|
||||||
|
else if (strcmp(media_str, "*/*") != 0 &&
|
||||||
|
strcmp(media_str, "text/html") != 0){
|
||||||
|
#ifdef NOTYET
|
||||||
|
clicon_log(LOG_NOTICE, "%s: media error %s", __FUNCTION__, media_str);
|
||||||
|
goto done;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
/* 6. Authenticate
|
||||||
|
* Note, error handling may need change since it is restconf based
|
||||||
|
*/
|
||||||
|
if ((ret = restconf_authentication_cb(h, req, 1, 0 /*media_out */)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0)
|
||||||
|
goto ok;
|
||||||
|
if (options){
|
||||||
|
if (restconf_reply_header(req, "Allow", "OPTIONS,HEAD,GET") < 0)
|
||||||
|
goto done;
|
||||||
|
if (restconf_reply_send(req, 200, NULL, 0) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_www_data_file(h, req, pathname, head) < 0)
|
||||||
|
goto done;
|
||||||
|
ok:
|
||||||
|
retval = 0;
|
||||||
|
done:
|
||||||
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
47
apps/restconf/clixon_www_data.h
Normal file
47
apps/restconf/clixon_www_data.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
***** BEGIN LICENSE BLOCK *****
|
||||||
|
|
||||||
|
Copyright (C) 2009-2019 Olof Hagsand
|
||||||
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||||
|
|
||||||
|
This file is part of CLIXON.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
Alternatively, the contents of this file may be used under the terms of
|
||||||
|
the GNU General Public License Version 3 or later (the "GPL"),
|
||||||
|
in which case the provisions of the GPL are applicable instead
|
||||||
|
of those above. If you wish to allow use of your version of this file only
|
||||||
|
under the terms of the GPL, and not to allow others to
|
||||||
|
use your version of this file under the terms of Apache License version 2,
|
||||||
|
indicate your decision by deleting the provisions above and replace them with
|
||||||
|
the notice and other provisions required by the GPL. If you do not delete
|
||||||
|
the provisions above, a recipient may use your version of this file under
|
||||||
|
the terms of any one of the Apache License version 2 or the GPL.
|
||||||
|
|
||||||
|
***** END LICENSE BLOCK *****
|
||||||
|
*
|
||||||
|
* Limited www data handler embedded in restconf code
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CLIXON_WWW_DATA_H_
|
||||||
|
#define _CLIXON_WWW_DATA_H_
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prototypes
|
||||||
|
*/
|
||||||
|
int api_path_is_data(clicon_handle h, char **data);
|
||||||
|
int api_www_data(clicon_handle h, void *req, cvec *qvec);
|
||||||
|
|
||||||
|
#endif /* _CLIXON_WWW_DATA_H_ */
|
||||||
|
|
@ -187,6 +187,7 @@ restconf_reply_body_add(void *req0,
|
||||||
* @param[in] req Fastcgi request handle
|
* @param[in] req Fastcgi request handle
|
||||||
* @param[in] code Status code
|
* @param[in] code Status code
|
||||||
* @param[in] cb Body as a cbuf if non-NULL. Note is consumed
|
* @param[in] cb Body as a cbuf if non-NULL. Note is consumed
|
||||||
|
* @param[in] head Only send headers, dont send body.
|
||||||
*
|
*
|
||||||
* Prerequisites: status code set, headers given, body if wanted set
|
* Prerequisites: status code set, headers given, body if wanted set
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,8 @@ restconf_reply_header(void *req0,
|
||||||
|
|
||||||
/*! Send HTTP reply with potential message body
|
/*! Send HTTP reply with potential message body
|
||||||
* @param[in] req http request handle
|
* @param[in] req http request handle
|
||||||
* @param[in] cb Body as a cbuf if non-NULL. Note: is consumed, dont free or reset after call
|
* @param[in] code Status code
|
||||||
|
* @param[in] cb Body as a cbuf if non-NULL. Note: is consumed
|
||||||
* @param[in] head Only send headers, dont send body.
|
* @param[in] head Only send headers, dont send body.
|
||||||
*
|
*
|
||||||
* Prerequisites: status code set, headers given, body if wanted set
|
* Prerequisites: status code set, headers given, body if wanted set
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@
|
||||||
#include "restconf_err.h"
|
#include "restconf_err.h"
|
||||||
#include "clixon_http1_parse.h"
|
#include "clixon_http1_parse.h"
|
||||||
#include "restconf_http1.h"
|
#include "restconf_http1.h"
|
||||||
|
#include "clixon_www_data.h"
|
||||||
|
|
||||||
/* Size of xml read buffer */
|
/* Size of xml read buffer */
|
||||||
#define BUFLEN 1024
|
#define BUFLEN 1024
|
||||||
|
|
@ -411,13 +412,27 @@ restconf_http1_path_root(clicon_handle h,
|
||||||
if (ret == 0) /* upgrade */
|
if (ret == 0) /* upgrade */
|
||||||
goto upgrade;
|
goto upgrade;
|
||||||
#endif
|
#endif
|
||||||
/* call generic function */
|
/* Matching algorithm:
|
||||||
|
* 1. try well-known
|
||||||
|
* 2. try /restconf
|
||||||
|
* 3. try /data
|
||||||
|
* 4. call restconf anyway (because it handles errors a la restconf)
|
||||||
|
* This is for the situation where data is / and /restconf is more specific
|
||||||
|
*/
|
||||||
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
||||||
if (api_well_known(h, sd) < 0)
|
if (api_well_known(h, sd) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
else if (api_path_is_restconf(h)){
|
||||||
goto done;
|
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_path_is_data(h, NULL)){
|
||||||
|
if (api_www_data(h, sd, sd->sd_qvec) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
||||||
|
goto done;
|
||||||
fail:
|
fail:
|
||||||
if (restconf_param_del_all(h) < 0)
|
if (restconf_param_del_all(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,8 @@ main(int argc,
|
||||||
char *dir;
|
char *dir;
|
||||||
int logdst = CLICON_LOG_SYSLOG;
|
int logdst = CLICON_LOG_SYSLOG;
|
||||||
yang_stmt *yspec = NULL;
|
yang_stmt *yspec = NULL;
|
||||||
char *stream_path;
|
char *query;
|
||||||
|
cvec *qvec;
|
||||||
int finish = 0;
|
int finish = 0;
|
||||||
char *str;
|
char *str;
|
||||||
clixon_plugin_t *cp = NULL;
|
clixon_plugin_t *cp = NULL;
|
||||||
|
|
@ -314,6 +315,7 @@ main(int argc,
|
||||||
char *inline_config = NULL;
|
char *inline_config = NULL;
|
||||||
size_t sz;
|
size_t sz;
|
||||||
|
|
||||||
|
|
||||||
/* In the startup, logs to stderr & debug flag set later */
|
/* In the startup, logs to stderr & debug flag set later */
|
||||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||||
|
|
||||||
|
|
@ -377,7 +379,6 @@ main(int argc,
|
||||||
if (clicon_options_main(h) < 0)
|
if (clicon_options_main(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
|
|
||||||
/* Now rest of options, some overwrite option file */
|
/* Now rest of options, some overwrite option file */
|
||||||
optind = 1;
|
optind = 1;
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
|
|
@ -595,36 +596,37 @@ main(int argc,
|
||||||
*/
|
*/
|
||||||
if (fcgi_params_set(h, req->envp) < 0)
|
if (fcgi_params_set(h, req->envp) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
|
if ((path = restconf_param_get(h, "REQUEST_URI")) == NULL){
|
||||||
clicon_debug(1, "path: %s", path);
|
clicon_debug(1, "NULL URI");
|
||||||
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0){
|
}
|
||||||
char *query = NULL;
|
else {
|
||||||
cvec *qvec = NULL;
|
/* Matching algorithm:
|
||||||
|
* 1. try well-known
|
||||||
|
* 2. try /restconf
|
||||||
|
* 3. try /stream
|
||||||
|
* 4. return error
|
||||||
|
*/
|
||||||
|
query = NULL;
|
||||||
|
qvec = NULL;
|
||||||
|
if (strcmp(path, RESTCONF_WELL_KNOWN) == 0){
|
||||||
|
if (api_well_known(h, req) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_path_is_restconf(h)){
|
||||||
query = restconf_param_get(h, "QUERY_STRING");
|
query = restconf_param_get(h, "QUERY_STRING");
|
||||||
if (query != NULL && strlen(query))
|
if (query != NULL && strlen(query))
|
||||||
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
api_root_restconf(h, req, qvec); /* This is the function */
|
if (api_root_restconf(h, req, qvec) < 0)
|
||||||
if (qvec){
|
goto done;
|
||||||
cvec_free(qvec);
|
|
||||||
qvec = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
|
else if (api_path_is_stream(h)){
|
||||||
char *query = NULL;
|
|
||||||
cvec *qvec = NULL;
|
|
||||||
query = restconf_param_get(h, "QUERY_STRING");
|
query = restconf_param_get(h, "QUERY_STRING");
|
||||||
if (query != NULL && strlen(query))
|
if (query != NULL && strlen(query))
|
||||||
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
api_stream(h, req, qvec, stream_path, &finish);
|
/* XXX doing goto done on error causes test errors */
|
||||||
if (qvec){
|
(void)api_stream(h, req, qvec, &finish);
|
||||||
cvec_free(qvec);
|
|
||||||
qvec = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
|
|
||||||
api_well_known(h, req); /* */
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
clicon_debug(1, "top-level %s not found", path);
|
clicon_debug(1, "top-level %s not found", path);
|
||||||
|
|
@ -637,9 +639,11 @@ main(int argc,
|
||||||
xerr = NULL;
|
xerr = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (qvec){
|
||||||
|
cvec_free(qvec);
|
||||||
|
qvec = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
clicon_debug(1, "NULL URI");
|
|
||||||
if (restconf_param_del_all(h) < 0)
|
if (restconf_param_del_all(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (finish)
|
if (finish)
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,9 @@
|
||||||
#include "restconf_err.h"
|
#include "restconf_err.h"
|
||||||
#include "restconf_root.h"
|
#include "restconf_root.h"
|
||||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||||
#ifdef HAVE_LIBNGHTTP2
|
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
|
||||||
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
||||||
|
#include "clixon_www_data.h"
|
||||||
|
|
||||||
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
|
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
|
||||||
|
|
||||||
|
|
@ -309,13 +310,27 @@ restconf_nghttp2_path(restconf_stream_data *sd)
|
||||||
if (restconf_connection_sanity(h, rc, sd) < 0)
|
if (restconf_connection_sanity(h, rc, sd) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (!rc->rc_exit){
|
if (!rc->rc_exit){
|
||||||
/* call generic function */
|
/* Matching algorithm:
|
||||||
|
* 1. try well-known
|
||||||
|
* 2. try /restconf
|
||||||
|
* 3. try /data
|
||||||
|
* 4. call restconf anyway (because it handles errors)
|
||||||
|
* This is for the situation where data is / and /restconf is more specific
|
||||||
|
*/
|
||||||
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
||||||
if (api_well_known(h, sd) < 0)
|
if (api_well_known(h, sd) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
else if (api_path_is_restconf(h)){
|
||||||
goto done;
|
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_path_is_data(h, NULL)){
|
||||||
|
if (api_www_data(h, sd, sd->sd_qvec) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
/* Clear (fcgi) paramaters from this request */
|
/* Clear (fcgi) paramaters from this request */
|
||||||
if (restconf_param_del_all(h) < 0)
|
if (restconf_param_del_all(h) < 0)
|
||||||
|
|
@ -458,8 +473,9 @@ http2_exec(restconf_conn *rc,
|
||||||
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
||||||
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 ||
|
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0
|
||||||
strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
|| strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||||
|
|| api_path_is_data(rc->rc_h, NULL)){
|
||||||
if (restconf_nghttp2_path(sd) < 0)
|
if (restconf_nghttp2_path(sd) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,28 @@
|
||||||
#include "restconf_methods_get.h"
|
#include "restconf_methods_get.h"
|
||||||
#include "restconf_methods_post.h"
|
#include "restconf_methods_post.h"
|
||||||
|
|
||||||
|
/*! Check if uri path denotes a restconf path
|
||||||
|
*
|
||||||
|
* @retval 0 No, not a restconf path
|
||||||
|
* @retval 1 Yes, a restconf path
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
api_path_is_restconf(clicon_handle h)
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
char *restconf_path = RESTCONF_API;
|
||||||
|
|
||||||
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
|
return 0;
|
||||||
|
if (strlen(path) < 1 + strlen(restconf_path)) /* "/" + restconf */
|
||||||
|
return 0;
|
||||||
|
if (path[0] != '/')
|
||||||
|
return 0;
|
||||||
|
if (strncmp(path+1, restconf_path, strlen(restconf_path)) != 0)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Determine the root of the RESTCONF API by accessing /.well-known
|
/*! Determine the root of the RESTCONF API by accessing /.well-known
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
|
int api_path_is_restconf(clicon_handle h);
|
||||||
int api_well_known(clicon_handle h, void *req);
|
int api_well_known(clicon_handle h, void *req);
|
||||||
int api_root_restconf(clicon_handle h, void *req, cvec *qvec);
|
int api_root_restconf(clicon_handle h, void *req, cvec *qvec);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,9 @@
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
|
int api_path_is_stream(clicon_handle h);
|
||||||
int stream_child_free(clicon_handle h, int pid);
|
int stream_child_free(clicon_handle h, int pid);
|
||||||
int stream_child_freeall(clicon_handle h);
|
int stream_child_freeall(clicon_handle h);
|
||||||
int api_stream(clicon_handle h, void *req, cvec *qvec, char *streampath, int *finish);
|
int api_stream(clicon_handle h, void *req, cvec *qvec, int *finish);
|
||||||
|
|
||||||
#endif /* _RESTCONF_STREAM_H_ */
|
#endif /* _RESTCONF_STREAM_H_ */
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,30 @@ struct stream_child{
|
||||||
*/
|
*/
|
||||||
static struct stream_child *STREAM_CHILD = NULL;
|
static struct stream_child *STREAM_CHILD = NULL;
|
||||||
|
|
||||||
|
/*! Check if uri path denotes a stream/notification path
|
||||||
|
*
|
||||||
|
* @retval 0 No, not a stream path
|
||||||
|
* @retval 1 Yes, a stream path
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
api_path_is_stream(clicon_handle h)
|
||||||
|
{
|
||||||
|
char *path;
|
||||||
|
char *stream_path;
|
||||||
|
|
||||||
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
|
return 0;
|
||||||
|
if ((stream_path = clicon_option_str(h, "CLICON_STREAM_PATH")) == NULL)
|
||||||
|
return 0;
|
||||||
|
if (strlen(path) < 1 + strlen(stream_path)) /* "/" + stream */
|
||||||
|
return 0;
|
||||||
|
if (path[0] != '/')
|
||||||
|
return 0;
|
||||||
|
if (strncmp(path+1, stream_path, strlen(stream_path)) != 0)
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Find restconf child using PID and cleanup FCGI Request data
|
/*! Find restconf child using PID and cleanup FCGI Request data
|
||||||
*
|
*
|
||||||
* For forked, called on SIGCHILD
|
* For forked, called on SIGCHILD
|
||||||
|
|
@ -372,14 +396,12 @@ stream_timeout(int s,
|
||||||
* @param[in] h Clicon handle
|
* @param[in] h Clicon handle
|
||||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||||
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
|
||||||
* @param[in] streampath URI path for streams, eg /streams, see CLICON_STREAM_PATH
|
|
||||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_stream(clicon_handle h,
|
api_stream(clicon_handle h,
|
||||||
void *req,
|
void *req,
|
||||||
cvec *qvec,
|
cvec *qvec,
|
||||||
char *streampath,
|
|
||||||
int *finish)
|
int *finish)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -397,12 +419,14 @@ api_stream(clicon_handle h,
|
||||||
int s = -1;
|
int s = -1;
|
||||||
int ret;
|
int ret;
|
||||||
cxobj *xerr = NULL;
|
cxobj *xerr = NULL;
|
||||||
|
char *streampath;
|
||||||
#ifdef STREAM_FORK
|
#ifdef STREAM_FORK
|
||||||
int pid;
|
int pid;
|
||||||
struct stream_child *sc;
|
struct stream_child *sc;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
|
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||||
if ((path = restconf_uripath(h)) == NULL)
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
pretty = restconf_pretty_get(h);
|
pretty = restconf_pretty_get(h);
|
||||||
|
|
|
||||||
|
|
@ -157,4 +157,3 @@
|
||||||
* If not set, client will exit
|
* If not set, client will exit
|
||||||
*/
|
*/
|
||||||
#define PROTO_RESTART_RECONNECT
|
#define PROTO_RESTART_RECONNECT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@ module clixon-config {
|
||||||
description
|
description
|
||||||
"Added option:
|
"Added option:
|
||||||
CLICON_NETCONF_BASE_CAPABILITY
|
CLICON_NETCONF_BASE_CAPABILITY
|
||||||
|
CLICON_WWW_DATA_PATH
|
||||||
|
CLICON_WWW_DATA_ROOT
|
||||||
|
Added feature:
|
||||||
|
www-data
|
||||||
Released in Clixon 5.7";
|
Released in Clixon 5.7";
|
||||||
}
|
}
|
||||||
revision 2022-02-11 {
|
revision 2022-02-11 {
|
||||||
|
|
@ -212,6 +216,20 @@ module clixon-config {
|
||||||
description
|
description
|
||||||
"Released in Clixon 3.8";
|
"Released in Clixon 3.8";
|
||||||
}
|
}
|
||||||
|
feature www-data {
|
||||||
|
description
|
||||||
|
"This feature allows for a very limited www-data function as
|
||||||
|
addition to RESTCONF.
|
||||||
|
it is limited to:
|
||||||
|
1. path: Local files within WWW_DATA_ROOT
|
||||||
|
2. operation GET or HEAD
|
||||||
|
3. query parameters not supported
|
||||||
|
4. indata should be NULL (no write operations)
|
||||||
|
5. Limited media: text/html, JavaScript, image, and css
|
||||||
|
6. Authentication as restconf
|
||||||
|
7. HTTP/1+2, TLS as restconf";
|
||||||
|
}
|
||||||
|
|
||||||
extension search_index {
|
extension search_index {
|
||||||
description "This list argument acts as a search index using optimized binary search.
|
description "This list argument acts as a search index using optimized binary search.
|
||||||
";
|
";
|
||||||
|
|
@ -601,6 +619,19 @@ module clixon-config {
|
||||||
Note this also disables plain http/2 in prior-knowledge, that is, in http/2-only mode.
|
Note this also disables plain http/2 in prior-knowledge, that is, in http/2-only mode.
|
||||||
HTTP/2 in https(TLS) is unaffected";
|
HTTP/2 in https(TLS) is unaffected";
|
||||||
}
|
}
|
||||||
|
leaf CLICON_WWW_DATA_PATH {
|
||||||
|
if-feature "www-data";
|
||||||
|
type string;
|
||||||
|
description
|
||||||
|
"If set, enable www data on this sub-path";
|
||||||
|
}
|
||||||
|
leaf CLICON_WWW_DATA_ROOT {
|
||||||
|
if-feature "www-data";
|
||||||
|
type string;
|
||||||
|
default "/var/www";
|
||||||
|
description
|
||||||
|
"public web root";
|
||||||
|
}
|
||||||
leaf CLICON_CLI_DIR {
|
leaf CLICON_CLI_DIR {
|
||||||
type string;
|
type string;
|
||||||
description
|
description
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,16 @@ module clixon-restconf {
|
||||||
|
|
||||||
description
|
description
|
||||||
"This YANG module provides a data-model for the Clixon RESTCONF daemon.
|
"This YANG module provides a data-model for the Clixon RESTCONF daemon.
|
||||||
|
There is also clixon-config also including some restconf options.
|
||||||
|
The separation is not always logical but there are some reasons for the split:
|
||||||
|
1. Some data (ie 'socket') is structurally complex and cannot be expressed as a
|
||||||
|
simple option
|
||||||
|
2. clixon-restconf is defined as a macro/grouping and can be included in
|
||||||
|
other YANGs. In particular, it can be used inside a datastore, which
|
||||||
|
is not possible for clixon-config.
|
||||||
|
3. Related to (2), options that should not be settable in a datastore should be
|
||||||
|
in clixon-config
|
||||||
|
|
||||||
***** BEGIN LICENSE BLOCK *****
|
***** BEGIN LICENSE BLOCK *****
|
||||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue