* 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
|
||||
|
||||
* 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.
|
||||
* First hello is 1.0 EOM framing, then successing rpc is chunked framing
|
||||
* See
|
||||
* [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)
|
||||
|
||||
###
|
||||
|
||||
### API changes on existing protocol/config features
|
||||
|
||||
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
|
||||
* Added option:
|
||||
* `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
|
||||
* CLI reconnects to backend if backend restarts with a warning
|
||||
* 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_patch.c
|
||||
APPSRC += restconf_root.c
|
||||
APPSRC += clixon_www_data.c
|
||||
APPSRC += restconf_main_$(with_restconf).c
|
||||
ifeq ($(with_restconf),native)
|
||||
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] 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.
|
||||
*
|
||||
* 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
|
||||
* @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.
|
||||
*
|
||||
* Prerequisites: status code set, headers given, body if wanted set
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@
|
|||
#include "restconf_err.h"
|
||||
#include "clixon_http1_parse.h"
|
||||
#include "restconf_http1.h"
|
||||
#include "clixon_www_data.h"
|
||||
|
||||
/* Size of xml read buffer */
|
||||
#define BUFLEN 1024
|
||||
|
|
@ -411,12 +412,26 @@ restconf_http1_path_root(clicon_handle h,
|
|||
if (ret == 0) /* upgrade */
|
||||
goto upgrade;
|
||||
#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 (api_well_known(h, sd) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||
else if (api_path_is_restconf(h)){
|
||||
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:
|
||||
if (restconf_param_del_all(h) < 0)
|
||||
|
|
|
|||
|
|
@ -301,7 +301,8 @@ main(int argc,
|
|||
char *dir;
|
||||
int logdst = CLICON_LOG_SYSLOG;
|
||||
yang_stmt *yspec = NULL;
|
||||
char *stream_path;
|
||||
char *query;
|
||||
cvec *qvec;
|
||||
int finish = 0;
|
||||
char *str;
|
||||
clixon_plugin_t *cp = NULL;
|
||||
|
|
@ -314,6 +315,7 @@ main(int argc,
|
|||
char *inline_config = NULL;
|
||||
size_t sz;
|
||||
|
||||
|
||||
/* In the startup, logs to stderr & debug flag set later */
|
||||
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
|
||||
|
||||
|
|
@ -377,7 +379,6 @@ main(int argc,
|
|||
if (clicon_options_main(h) < 0)
|
||||
goto done;
|
||||
|
||||
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||
/* Now rest of options, some overwrite option file */
|
||||
optind = 1;
|
||||
opterr = 0;
|
||||
|
|
@ -595,36 +596,37 @@ main(int argc,
|
|||
*/
|
||||
if (fcgi_params_set(h, req->envp) < 0)
|
||||
goto done;
|
||||
if ((path = restconf_param_get(h, "REQUEST_URI")) != NULL){
|
||||
clicon_debug(1, "path: %s", path);
|
||||
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0){
|
||||
char *query = NULL;
|
||||
cvec *qvec = NULL;
|
||||
if ((path = restconf_param_get(h, "REQUEST_URI")) == NULL){
|
||||
clicon_debug(1, "NULL URI");
|
||||
}
|
||||
else {
|
||||
/* 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");
|
||||
if (query != NULL && strlen(query))
|
||||
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
||||
goto done;
|
||||
api_root_restconf(h, req, qvec); /* This is the function */
|
||||
if (qvec){
|
||||
cvec_free(qvec);
|
||||
qvec = NULL;
|
||||
if (api_root_restconf(h, req, qvec) < 0)
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
|
||||
char *query = NULL;
|
||||
cvec *qvec = NULL;
|
||||
else if (api_path_is_stream(h)){
|
||||
query = restconf_param_get(h, "QUERY_STRING");
|
||||
if (query != NULL && strlen(query))
|
||||
if (uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
|
||||
goto done;
|
||||
api_stream(h, req, qvec, stream_path, &finish);
|
||||
if (qvec){
|
||||
cvec_free(qvec);
|
||||
qvec = NULL;
|
||||
}
|
||||
}
|
||||
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
|
||||
api_well_known(h, req); /* */
|
||||
/* XXX doing goto done on error causes test errors */
|
||||
(void)api_stream(h, req, qvec, &finish);
|
||||
}
|
||||
else{
|
||||
clicon_debug(1, "top-level %s not found", path);
|
||||
|
|
@ -637,9 +639,11 @@ main(int argc,
|
|||
xerr = NULL;
|
||||
}
|
||||
}
|
||||
if (qvec){
|
||||
cvec_free(qvec);
|
||||
qvec = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
clicon_debug(1, "NULL URI");
|
||||
if (restconf_param_del_all(h) < 0)
|
||||
goto done;
|
||||
if (finish)
|
||||
|
|
|
|||
|
|
@ -85,8 +85,9 @@
|
|||
#include "restconf_err.h"
|
||||
#include "restconf_root.h"
|
||||
#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 "clixon_www_data.h"
|
||||
|
||||
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
|
|
@ -309,12 +310,26 @@ restconf_nghttp2_path(restconf_stream_data *sd)
|
|||
if (restconf_connection_sanity(h, rc, sd) < 0)
|
||||
goto done;
|
||||
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 (api_well_known(h, sd) < 0)
|
||||
goto done;
|
||||
}
|
||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||
else if (api_path_is_restconf(h)){
|
||||
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 */
|
||||
|
|
@ -458,8 +473,9 @@ http2_exec(restconf_conn *rc,
|
|||
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||
goto done;
|
||||
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
|
||||
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 ||
|
||||
strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
|
||||
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0
|
||||
|| strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||
|| api_path_is_data(rc->rc_h, NULL)){
|
||||
if (restconf_nghttp2_path(sd) < 0)
|
||||
goto done;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,28 @@
|
|||
#include "restconf_methods_get.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
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int api_path_is_restconf(clicon_handle h);
|
||||
int api_well_known(clicon_handle h, void *req);
|
||||
int api_root_restconf(clicon_handle h, void *req, cvec *qvec);
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@
|
|||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int api_path_is_stream(clicon_handle h);
|
||||
int stream_child_free(clicon_handle h, int pid);
|
||||
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_ */
|
||||
|
|
|
|||
|
|
@ -118,6 +118,30 @@ struct stream_child{
|
|||
*/
|
||||
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
|
||||
*
|
||||
* For forked, called on SIGCHILD
|
||||
|
|
@ -372,14 +396,12 @@ stream_timeout(int s,
|
|||
* @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] 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
|
||||
*/
|
||||
int
|
||||
api_stream(clicon_handle h,
|
||||
void *req,
|
||||
cvec *qvec,
|
||||
char *streampath,
|
||||
int *finish)
|
||||
{
|
||||
int retval = -1;
|
||||
|
|
@ -397,12 +419,14 @@ api_stream(clicon_handle h,
|
|||
int s = -1;
|
||||
int ret;
|
||||
cxobj *xerr = NULL;
|
||||
char *streampath;
|
||||
#ifdef STREAM_FORK
|
||||
int pid;
|
||||
struct stream_child *sc;
|
||||
#endif
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
||||
if ((path = restconf_uripath(h)) == NULL)
|
||||
goto done;
|
||||
pretty = restconf_pretty_get(h);
|
||||
|
|
|
|||
|
|
@ -157,4 +157,3 @@
|
|||
* If not set, client will exit
|
||||
*/
|
||||
#define PROTO_RESTART_RECONNECT
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ module clixon-config {
|
|||
description
|
||||
"Added option:
|
||||
CLICON_NETCONF_BASE_CAPABILITY
|
||||
CLICON_WWW_DATA_PATH
|
||||
CLICON_WWW_DATA_ROOT
|
||||
Added feature:
|
||||
www-data
|
||||
Released in Clixon 5.7";
|
||||
}
|
||||
revision 2022-02-11 {
|
||||
|
|
@ -212,6 +216,20 @@ module clixon-config {
|
|||
description
|
||||
"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 {
|
||||
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.
|
||||
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 {
|
||||
type string;
|
||||
description
|
||||
|
|
|
|||
|
|
@ -15,6 +15,16 @@ module clixon-restconf {
|
|||
|
||||
description
|
||||
"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 *****
|
||||
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue