* 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:
Olof hagsand 2022-04-19 09:24:40 +02:00
parent e1bec5f6dd
commit 76213057b6
16 changed files with 572 additions and 41 deletions

View file

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

View file

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

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

View 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_ */

View file

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

View file

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

View file

@ -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,12 +412,26 @@ 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)){
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; goto done;
fail: fail:
if (restconf_param_del_all(h) < 0) if (restconf_param_del_all(h) < 0)

View file

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

View file

@ -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,12 +310,26 @@ 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)){
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; goto done;
} }
/* Clear (fcgi) paramaters from this request */ /* 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) 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;
} }

View file

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

View file

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

View file

@ -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_ */

View file

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

View file

@ -157,4 +157,3 @@
* If not set, client will exit * If not set, client will exit
*/ */
#define PROTO_RESTART_RECONNECT #define PROTO_RESTART_RECONNECT

View file

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

View file

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