clixon/apps/restconf/clixon_www_data.c
Olof hagsand 76213057b6 * 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
2022-04-21 16:01:33 +02:00

342 lines
9.9 KiB
C

/*
*
***** 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;
}