From 76213057b66f766382c3a25738dbcc6578a0751b Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 19 Apr 2022 09:24:40 +0200 Subject: [PATCH] * 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 --- CHANGELOG.md | 20 +- apps/restconf/Makefile.in | 1 + apps/restconf/clixon_www_data.c | 342 ++++++++++++++++++++ apps/restconf/clixon_www_data.h | 47 +++ apps/restconf/restconf_api_fcgi.c | 1 + apps/restconf/restconf_api_native.c | 3 +- apps/restconf/restconf_http1.c | 21 +- apps/restconf/restconf_main_fcgi.c | 54 ++-- apps/restconf/restconf_nghttp2.c | 28 +- apps/restconf/restconf_root.c | 22 ++ apps/restconf/restconf_root.h | 1 + apps/restconf/restconf_stream.h | 3 +- apps/restconf/restconf_stream_fcgi.c | 28 +- include/clixon_custom.h | 1 - yang/clixon/clixon-config@2022-03-21.yang | 31 ++ yang/clixon/clixon-restconf@2021-05-20.yang | 10 + 16 files changed, 572 insertions(+), 41 deletions(-) create mode 100644 apps/restconf/clixon_www_data.c create mode 100644 apps/restconf/clixon_www_data.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b259b90..ace0acfd 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index b7b3212f..29c09653 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -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 diff --git a/apps/restconf/clixon_www_data.c b/apps/restconf/clixon_www_data.c new file mode 100644 index 00000000..8dad80c1 --- /dev/null +++ b/apps/restconf/clixon_www_data.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* chmod */ + +/* cligen */ +#include + +/* clicon */ +#include + +/* 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, "\r\n"); + cprintf(cb, "\r\n"); + cprintf(cb, "%d %s\r\n", code, restconf_code2reason(code)); + cprintf(cb, "\r\n"); + cprintf(cb, "

%s

\r\n", restconf_code2reason(code)); +#if 0 /* Cant find this sentence in any RFC, is it ad-hoc? */ + cprintf(cb, "

The requested URL was not found on this server.

\r\n"); +#endif + cprintf(cb, "\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 ?=&= 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; +} diff --git a/apps/restconf/clixon_www_data.h b/apps/restconf/clixon_www_data.h new file mode 100644 index 00000000..fc971692 --- /dev/null +++ b/apps/restconf/clixon_www_data.h @@ -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_ */ diff --git a/apps/restconf/restconf_api_fcgi.c b/apps/restconf/restconf_api_fcgi.c index b65ff843..a8a440e0 100644 --- a/apps/restconf/restconf_api_fcgi.c +++ b/apps/restconf/restconf_api_fcgi.c @@ -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 */ diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 46fd7526..4bbd800b 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -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 diff --git a/apps/restconf/restconf_http1.c b/apps/restconf/restconf_http1.c index b1f3251c..b0d238c1 100644 --- a/apps/restconf/restconf_http1.c +++ b/apps/restconf/restconf_http1.c @@ -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,13 +412,27 @@ 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) - goto done; + 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) goto done; diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index a072d780..8890f3af 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -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) diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index 9ec1b150..acf3e005 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -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,13 +310,27 @@ 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) - goto done; + 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 */ 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) 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; } diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 734a30c4..c75d5b85 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -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) diff --git a/apps/restconf/restconf_root.h b/apps/restconf/restconf_root.h index b2012631..76e9edbe 100644 --- a/apps/restconf/restconf_root.h +++ b/apps/restconf/restconf_root.h @@ -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); diff --git a/apps/restconf/restconf_stream.h b/apps/restconf/restconf_stream.h index 50491df2..c4ad9a1c 100644 --- a/apps/restconf/restconf_stream.h +++ b/apps/restconf/restconf_stream.h @@ -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_ */ diff --git a/apps/restconf/restconf_stream_fcgi.c b/apps/restconf/restconf_stream_fcgi.c index 147391d1..2792168c 100644 --- a/apps/restconf/restconf_stream_fcgi.c +++ b/apps/restconf/restconf_stream_fcgi.c @@ -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 ?=&= 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); diff --git a/include/clixon_custom.h b/include/clixon_custom.h index 6efa7c9d..16a3700b 100644 --- a/include/clixon_custom.h +++ b/include/clixon_custom.h @@ -157,4 +157,3 @@ * If not set, client will exit */ #define PROTO_RESTART_RECONNECT - diff --git a/yang/clixon/clixon-config@2022-03-21.yang b/yang/clixon/clixon-config@2022-03-21.yang index c4d7af75..2f184f61 100644 --- a/yang/clixon/clixon-config@2022-03-21.yang +++ b/yang/clixon/clixon-config@2022-03-21.yang @@ -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 diff --git a/yang/clixon/clixon-restconf@2021-05-20.yang b/yang/clixon/clixon-restconf@2021-05-20.yang index 5f48c1d2..53cf34cc 100644 --- a/yang/clixon/clixon-restconf@2021-05-20.yang +++ b/yang/clixon/clixon-restconf@2021-05-20.yang @@ -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)