Restconf http-data server updates
Check data paths for .., ~ and soft links Changed semantics of `CLICON_HTTP_DATA_PATH` and `_ROOT` Change URI catch-all to 404 instead of 400 Fixed some memory leaks
This commit is contained in:
parent
0ed8b836b1
commit
84d88c8ad8
12 changed files with 303 additions and 106 deletions
|
|
@ -86,32 +86,34 @@ static const map_str2str mime_map[] = {
|
||||||
|
|
||||||
/*! Check if uri path denotes a data path
|
/*! Check if uri path denotes a data path
|
||||||
*
|
*
|
||||||
* @param[out] data Pointer to string where data starts if retval = 1
|
* @param[in] h Clixon handle
|
||||||
* @retval 0 No, not a data path, or not enabled
|
* @retval 0 No, not a data path, or not enabled
|
||||||
* @retval 1 Yes, a data path and "data" points to www-data if given
|
* @retval 1 Yes, a data path and "data" points to www-data if given
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_path_is_data(clicon_handle h,
|
api_path_is_data(clicon_handle h)
|
||||||
char **data)
|
|
||||||
{
|
{
|
||||||
char *path;
|
int retval = 0;
|
||||||
|
char *path = NULL;
|
||||||
char *http_data_path;
|
char *http_data_path;
|
||||||
|
|
||||||
if (restconf_http_data_get(h) == 0)
|
if (restconf_http_data_get(h) == 0)
|
||||||
return 0;
|
goto done;
|
||||||
if ((path = restconf_uripath(h)) == NULL)
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
return 0;
|
goto done;
|
||||||
if ((http_data_path = clicon_option_str(h, "CLICON_HTTP_DATA_PATH")) == NULL)
|
if ((http_data_path = clicon_option_str(h, "CLICON_HTTP_DATA_PATH")) == NULL)
|
||||||
return 0;
|
goto done;
|
||||||
if (strlen(path) < strlen(http_data_path))
|
if (strlen(path) < strlen(http_data_path))
|
||||||
return 0;
|
goto done;
|
||||||
if (path[0] != '/')
|
if (path[0] != '/')
|
||||||
return 0;
|
goto done;
|
||||||
if (strncmp(path, http_data_path, strlen(http_data_path)) != 0)
|
if (strncmp(path, http_data_path, strlen(http_data_path)) != 0)
|
||||||
return 0;
|
goto done;
|
||||||
if (data)
|
retval = 1;
|
||||||
*data = path + strlen(http_data_path);
|
done:
|
||||||
return 1;
|
if (path)
|
||||||
|
free(path);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Generic restconf error function on get/head request
|
/*! Generic restconf error function on get/head request
|
||||||
|
|
@ -156,6 +158,75 @@ api_http_data_err(clicon_handle h,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*! Check validity of path, may only be regular dir or file
|
||||||
|
* No .., soft link, ~, etc
|
||||||
|
* @param[in] prefix Prefix of path0, where to start file check
|
||||||
|
* @param[in] path0 Filepath
|
||||||
|
* @param[out] code Error code, if retval = 0
|
||||||
|
* @retval -1 Error
|
||||||
|
* @retval 0 Invalid, code set
|
||||||
|
* @retval 1 OK
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
check_file_path(char *prefix,
|
||||||
|
char *path0,
|
||||||
|
int *code)
|
||||||
|
{
|
||||||
|
int retval = -1;
|
||||||
|
char *path = NULL;
|
||||||
|
int i;
|
||||||
|
struct stat fstat;
|
||||||
|
|
||||||
|
if (prefix == NULL || path0 == NULL || code == NULL){
|
||||||
|
clicon_err(OE_UNIX, EINVAL, "prefix, path0 or code is NULL");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if ((path = strdup(path0)) == NULL){
|
||||||
|
clicon_err(OE_UNIX, errno, "strdup");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
for (i=strlen(prefix); i<strlen(path0); i++){
|
||||||
|
if (path[i] == '/'){ /* Check valid dir */
|
||||||
|
path[i] = '\0';
|
||||||
|
/* Ensure not soft link */
|
||||||
|
if (lstat(path, &fstat) < 0){
|
||||||
|
*code = 404;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
if (!S_ISDIR(fstat.st_mode)){
|
||||||
|
*code = 403;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
path[i] = '/';
|
||||||
|
}
|
||||||
|
else if (path[i] == '~'){
|
||||||
|
*code = 403;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
else if (path[i] == '.' && i>strlen(prefix) && path[i-1] == '.'){
|
||||||
|
*code = 403;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Resulting file (ensure not soft link) */
|
||||||
|
if (lstat(path, &fstat) < 0){
|
||||||
|
*code = 404;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
if (!S_ISREG(fstat.st_mode)){
|
||||||
|
*code = 403;
|
||||||
|
goto invalid;
|
||||||
|
}
|
||||||
|
retval = 1; /* OK */
|
||||||
|
done:
|
||||||
|
if (path)
|
||||||
|
free(path);
|
||||||
|
return retval;
|
||||||
|
invalid:
|
||||||
|
retval = 0;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
/*! Read file data request
|
/*! Read file data request
|
||||||
* @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)
|
||||||
|
|
@ -167,21 +238,23 @@ api_http_data_err(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
api_http_data_file(clicon_handle h,
|
api_http_data_file(clicon_handle h,
|
||||||
void *req,
|
void *req,
|
||||||
char *pathname,
|
char *pathname,
|
||||||
int head)
|
int head)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cbuf *cbfile = NULL;
|
cbuf *cbfile = NULL;
|
||||||
char *filename;
|
char *filename;
|
||||||
struct stat fstat;
|
|
||||||
cbuf *cbdata = NULL;
|
cbuf *cbdata = NULL;
|
||||||
FILE *f = NULL;
|
FILE *f = NULL;
|
||||||
long fsize;
|
long fsize;
|
||||||
size_t sz;
|
|
||||||
char *www_data_root = NULL;
|
char *www_data_root = NULL;
|
||||||
char *suffix;
|
char *suffix;
|
||||||
char *media;
|
char *media;
|
||||||
|
int ret;
|
||||||
|
int code = 0;
|
||||||
|
char *str = NULL;
|
||||||
|
size_t sz;
|
||||||
|
|
||||||
if ((cbfile = cbuf_new()) == NULL){
|
if ((cbfile = cbuf_new()) == NULL){
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
|
|
@ -204,8 +277,12 @@ api_http_data_file(clicon_handle h,
|
||||||
if (pathname)
|
if (pathname)
|
||||||
cprintf(cbfile, "/%s", pathname);
|
cprintf(cbfile, "/%s", pathname);
|
||||||
filename = cbuf_get(cbfile);
|
filename = cbuf_get(cbfile);
|
||||||
if (stat(filename, &fstat) < 0){
|
clicon_debug(1, "%s %s", __FUNCTION__, filename);
|
||||||
if (api_http_data_err(h, req, 404) < 0) /* not found */
|
if ((ret = check_file_path(www_data_root, filename, &code)) < 0)
|
||||||
|
goto done;
|
||||||
|
if (ret == 0){
|
||||||
|
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||||
|
if (api_http_data_err(h, req, code) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
@ -214,6 +291,9 @@ api_http_data_file(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
/* Size could have been taken from stat() but this reduces the race condition interval
|
||||||
|
* There is still one without flock
|
||||||
|
*/
|
||||||
fseek(f, 0, SEEK_END);
|
fseek(f, 0, SEEK_END);
|
||||||
fsize = ftell(f);
|
fsize = ftell(f);
|
||||||
fseek(f, 0, SEEK_SET); /* same as rewind(f); */
|
fseek(f, 0, SEEK_SET); /* same as rewind(f); */
|
||||||
|
|
@ -221,31 +301,28 @@ api_http_data_file(clicon_handle h,
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new_alloc");
|
clicon_err(OE_UNIX, errno, "cbuf_new_alloc");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#if 0 /* Direct read but cannot set cb_len via API */
|
/* Unoptimized, no direct read but requires an extra copy,
|
||||||
fread(cbuf_get(cbdata), fsize, 1, f);
|
* the cligen buf API should have some mechanism for this case without the extra copy.
|
||||||
#else
|
*/
|
||||||
{
|
if ((str = malloc(fsize + 1)) == NULL){
|
||||||
char *str;
|
clicon_err(OE_UNIX, errno, "malloc");
|
||||||
if ((str = malloc(fsize + 1)) == NULL){
|
goto done;
|
||||||
clicon_err(OE_UNIX, errno, "malloc");
|
}
|
||||||
goto done;
|
if ((sz = fread(str, fsize, 1, f)) < 0){
|
||||||
}
|
clicon_err(OE_UNIX, errno, "fread");
|
||||||
if ((sz = fread(str, fsize, 1, f)) < 0){
|
goto done;
|
||||||
clicon_err(OE_UNIX, errno, "fread");
|
}
|
||||||
goto done;
|
if (sz != 1){
|
||||||
}
|
if (api_http_data_err(h, req, 500) < 0) /* Internal error? */
|
||||||
if (sz != 1){
|
goto done;
|
||||||
clicon_log(LOG_NOTICE, "%s: file read %s", __FUNCTION__, filename);
|
goto ok;
|
||||||
// XXX error handling: file read
|
}
|
||||||
goto done;
|
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
|
||||||
}
|
str[fsize] = 0;
|
||||||
str[fsize] = 0;
|
if (cbuf_append_str(cbdata, str) < 0){
|
||||||
if (cbuf_append_str(cbdata, str) < 0){
|
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_append_str");
|
goto done;
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (restconf_reply_header(req, "Content-Type", "%s", media) < 0)
|
if (restconf_reply_header(req, "Content-Type", "%s", media) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (restconf_reply_send(req, 200, cbdata, head) < 0)
|
if (restconf_reply_send(req, 200, cbdata, head) < 0)
|
||||||
|
|
@ -254,6 +331,8 @@ api_http_data_file(clicon_handle h,
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (str)
|
||||||
|
free(str);
|
||||||
if (f)
|
if (f)
|
||||||
fclose(f);
|
fclose(f);
|
||||||
if (cbfile)
|
if (cbfile)
|
||||||
|
|
@ -291,7 +370,7 @@ api_http_data(clicon_handle h,
|
||||||
int options = 0;
|
int options = 0;
|
||||||
int ret;
|
int ret;
|
||||||
cbuf *indata = NULL;
|
cbuf *indata = NULL;
|
||||||
char *pathname = NULL;
|
char *path = NULL;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if (req == NULL){
|
if (req == NULL){
|
||||||
|
|
@ -300,11 +379,12 @@ api_http_data(clicon_handle h,
|
||||||
}
|
}
|
||||||
/* 1. path: with stripped prefix, ultimately: dir/filename
|
/* 1. path: with stripped prefix, ultimately: dir/filename
|
||||||
*/
|
*/
|
||||||
if (!api_path_is_data(h, &pathname)){
|
if (!api_path_is_data(h)){
|
||||||
if (api_http_data_err(h, req, 404) < 0) /* not found */
|
if (api_http_data_err(h, req, 404) < 0) /* not found */
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
path = restconf_uripath(h);
|
||||||
/* 2. operation GET or HEAD */
|
/* 2. operation GET or HEAD */
|
||||||
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
||||||
if (strcmp(request_method, "GET") == 0){
|
if (strcmp(request_method, "GET") == 0){
|
||||||
|
|
@ -360,11 +440,13 @@ api_http_data(clicon_handle h,
|
||||||
if (restconf_reply_send(req, 200, NULL, 0) < 0)
|
if (restconf_reply_send(req, 200, NULL, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (api_http_data_file(h, req, pathname, head) < 0)
|
else if (api_http_data_file(h, req, path, head) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (path)
|
||||||
|
free(path);
|
||||||
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
clicon_debug(1, "%s %d", __FUNCTION__, retval);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
int api_path_is_data(clicon_handle h, char **data);
|
int api_path_is_data(clicon_handle h);
|
||||||
int api_http_data(clicon_handle h, void *req, cvec *qvec);
|
int api_http_data(clicon_handle h, void *req, cvec *qvec);
|
||||||
|
|
||||||
#endif /* _CLIXON_HTTP_DATA_H_ */
|
#endif /* _CLIXON_HTTP_DATA_H_ */
|
||||||
|
|
|
||||||
|
|
@ -345,7 +345,7 @@ restconf_http1_path_root(clicon_handle h,
|
||||||
restconf_conn *rc)
|
restconf_conn *rc)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_stream_data *sd;
|
restconf_stream_data *sd = NULL;
|
||||||
cvec *cvv = NULL;
|
cvec *cvv = NULL;
|
||||||
char *cn;
|
char *cn;
|
||||||
char *subject = NULL;
|
char *subject = NULL;
|
||||||
|
|
@ -380,6 +380,13 @@ restconf_http1_path_root(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
#if 1
|
||||||
|
/* XXX gives mem leak in multiple requests,
|
||||||
|
* but maybe the error is that sd is not freed.
|
||||||
|
*/
|
||||||
|
if (sd->sd_path != NULL)
|
||||||
|
free(sd->sd_path);
|
||||||
|
#endif
|
||||||
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
|
||||||
goto done; // XXX SHOULDNT EXIT if no REQUEST_URI
|
goto done; // XXX SHOULDNT EXIT if no REQUEST_URI
|
||||||
if (rc->rc_proto_d2 == 0 && rc->rc_proto == HTTP_11)
|
if (rc->rc_proto_d2 == 0 && rc->rc_proto == HTTP_11)
|
||||||
|
|
@ -427,7 +434,7 @@ restconf_http1_path_root(clicon_handle h,
|
||||||
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (api_path_is_data(h, NULL)){
|
else if (api_path_is_data(h)){
|
||||||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ restconf_nghttp2_path(restconf_stream_data *sd)
|
||||||
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
if (api_root_restconf(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (api_path_is_data(h, NULL)){
|
else if (api_path_is_data(h)){
|
||||||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -475,14 +475,17 @@ http2_exec(restconf_conn *rc,
|
||||||
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)){
|
|| api_path_is_data(rc->rc_h)){
|
||||||
if (restconf_nghttp2_path(sd) < 0)
|
if (restconf_nghttp2_path(sd) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
sd->sd_code = 400;
|
sd->sd_code = 404; /* not found */
|
||||||
; /* ignore */
|
|
||||||
}
|
}
|
||||||
|
if (restconf_param_del_all(rc->rc_h) < 0) // XXX
|
||||||
|
goto done;
|
||||||
|
|
||||||
/* If body, add a content-length header
|
/* If body, add a content-length header
|
||||||
* A server MUST NOT send a Content-Length header field in any response
|
* A server MUST NOT send a Content-Length header field in any response
|
||||||
* with a status code of 1xx (Informational) or 204 (No Content). A
|
* with a status code of 1xx (Informational) or 204 (No Content). A
|
||||||
|
|
|
||||||
|
|
@ -78,18 +78,23 @@
|
||||||
int
|
int
|
||||||
api_path_is_restconf(clicon_handle h)
|
api_path_is_restconf(clicon_handle h)
|
||||||
{
|
{
|
||||||
char *path;
|
int retval = 0;
|
||||||
|
char *path = NULL;
|
||||||
char *restconf_path = RESTCONF_API;
|
char *restconf_path = RESTCONF_API;
|
||||||
|
|
||||||
if ((path = restconf_uripath(h)) == NULL)
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
return 0;
|
goto done;
|
||||||
if (strlen(path) < 1 + strlen(restconf_path)) /* "/" + restconf */
|
if (strlen(path) < 1 + strlen(restconf_path)) /* "/" + restconf */
|
||||||
return 0;
|
goto done;
|
||||||
if (path[0] != '/')
|
if (path[0] != '/')
|
||||||
return 0;
|
goto done;
|
||||||
if (strncmp(path+1, restconf_path, strlen(restconf_path)) != 0)
|
if (strncmp(path+1, restconf_path, strlen(restconf_path)) != 0)
|
||||||
return 0;
|
goto done;
|
||||||
return 1;
|
retval = 1;
|
||||||
|
done:
|
||||||
|
if (path)
|
||||||
|
free(path);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Determine the root of the RESTCONF API by accessing /.well-known
|
/*! Determine the root of the RESTCONF API by accessing /.well-known
|
||||||
|
|
|
||||||
|
|
@ -126,20 +126,26 @@ static struct stream_child *STREAM_CHILD = NULL;
|
||||||
int
|
int
|
||||||
api_path_is_stream(clicon_handle h)
|
api_path_is_stream(clicon_handle h)
|
||||||
{
|
{
|
||||||
char *path;
|
int retval = 0;
|
||||||
|
char *path = NULL;
|
||||||
char *stream_path;
|
char *stream_path;
|
||||||
|
|
||||||
if ((path = restconf_uripath(h)) == NULL)
|
if ((path = restconf_uripath(h)) == NULL)
|
||||||
return 0;
|
goto done;
|
||||||
if ((stream_path = clicon_option_str(h, "CLICON_STREAM_PATH")) == NULL)
|
if ((stream_path = clicon_option_str(h, "CLICON_STREAM_PATH")) == NULL)
|
||||||
return 0;
|
goto done;
|
||||||
if (strlen(path) < 1 + strlen(stream_path)) /* "/" + stream */
|
if (strlen(path) < 1 + strlen(stream_path)) /* "/" + stream */
|
||||||
return 0;
|
goto done;
|
||||||
if (path[0] != '/')
|
if (path[0] != '/')
|
||||||
return 0;
|
goto done;
|
||||||
if (strncmp(path+1, stream_path, strlen(stream_path)) != 0)
|
if (strncmp(path+1, stream_path, strlen(stream_path)) != 0)
|
||||||
return 0;
|
goto done;
|
||||||
return 1;
|
retval = 1;
|
||||||
|
done:
|
||||||
|
if (path)
|
||||||
|
free(path);
|
||||||
|
return retval;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Find restconf child using PID and cleanup FCGI Request data
|
/*! Find restconf child using PID and cleanup FCGI Request data
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ AC_DEFINE_UNQUOTED(CLIXON_VERSION_PATCH, $CLIXON_VERSION_PATCH, [Clixon path ver
|
||||||
|
|
||||||
AC_CHECK_LIB(m, main)
|
AC_CHECK_LIB(m, main)
|
||||||
|
|
||||||
|
# defines: target_cpu, target_vendor, and target_os.
|
||||||
AC_CANONICAL_TARGET
|
AC_CANONICAL_TARGET
|
||||||
|
|
||||||
# AC_SUBST(var) makes @var@ appear in makefiles.
|
# AC_SUBST(var) makes @var@ appear in makefiles.
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,16 @@ Use:
|
||||||
- PRIu64 for uint64
|
- PRIu64 for uint64
|
||||||
- %p for pointers
|
- %p for pointers
|
||||||
|
|
||||||
|
### Include files
|
||||||
|
|
||||||
|
Avoid include statements in .h files, place them in .c files whenever possible.
|
||||||
|
|
||||||
|
The reason is to avoid deep include chains where file dependencies are
|
||||||
|
difficult to analyze and understand. If include statements are only placed in .c
|
||||||
|
files, there is only a single level of include file dependencies.
|
||||||
|
|
||||||
|
The drawback is that the same include file may need to be repeated in many .c files.
|
||||||
|
|
||||||
## How to work in git
|
## How to work in git
|
||||||
|
|
||||||
Clixon uses semantic versioning (https://semver.org).
|
Clixon uses semantic versioning (https://semver.org).
|
||||||
|
|
|
||||||
11
test/lib.sh
11
test/lib.sh
|
|
@ -94,6 +94,17 @@ DEFAULTNS="$DEFAULTONLY message-id=\"42\""
|
||||||
# Minimal hello message as a prelude to netconf rpcs
|
# Minimal hello message as a prelude to netconf rpcs
|
||||||
DEFAULTHELLO="<?xml version=\"1.0\" encoding=\"UTF-8\"?><hello $DEFAULTNS><capabilities><capability>urn:ietf:params:netconf:base:1.0</capability><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>"
|
DEFAULTHELLO="<?xml version=\"1.0\" encoding=\"UTF-8\"?><hello $DEFAULTNS><capabilities><capability>urn:ietf:params:netconf:base:1.0</capability><capability>urn:ietf:params:netconf:base:1.1</capability></capabilities></hello>]]>]]>"
|
||||||
|
|
||||||
|
# XXX cannot get this to work for all combinations of nc/netcat fcgi/native
|
||||||
|
# But leave it here for debugging where netcat works properly
|
||||||
|
if [ -n "$(type netcat 2> /dev/null)" ]; then
|
||||||
|
netcat="netcat -w 1" # -N does not work on fcgi
|
||||||
|
# nc on freebsd does not work either
|
||||||
|
#elif [ -n "$(type nc 2> /dev/null)" ]; then
|
||||||
|
# netcat=nc
|
||||||
|
else
|
||||||
|
netcat=
|
||||||
|
fi
|
||||||
|
|
||||||
# Options passed to curl calls
|
# Options passed to curl calls
|
||||||
# -s : silent
|
# -s : silent
|
||||||
# -S : show error
|
# -S : show error
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ APPNAME=example
|
||||||
cfg=$dir/conf.xml
|
cfg=$dir/conf.xml
|
||||||
rm -rf $dir/www
|
rm -rf $dir/www
|
||||||
mkdir $dir/www
|
mkdir $dir/www
|
||||||
|
mkdir $dir/www/data
|
||||||
|
|
||||||
# Does not work with fcgi
|
# Does not work with fcgi
|
||||||
if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
||||||
|
|
@ -22,7 +23,7 @@ if [ "${WITH_RESTCONF}" = "fcgi" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Data file
|
# Data file
|
||||||
cat <<EOF > $dir/www/index.html
|
cat <<EOF > $dir/www/data/index.html
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -43,7 +44,7 @@ working. Further configuration is required.</p>
|
||||||
</html>
|
</html>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > $dir/www/example.css
|
cat <<EOF > $dir/www/data/example.css
|
||||||
img {
|
img {
|
||||||
display: inline;
|
display: inline;
|
||||||
border:
|
border:
|
||||||
|
|
@ -67,6 +68,54 @@ h1,h2,h3,h4,h5,h6 {
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Outside wwwdir, should not be able to access this
|
||||||
|
cat <<EOF > $dir/outside.html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Dont access this</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
width: 35em;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dont access this!</h1>
|
||||||
|
<p>If you see this page, you accessed a file outside the root domain</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create a soft link from inside to outside
|
||||||
|
ln -s $dir/outside.html $dir/www/data/inside.html
|
||||||
|
|
||||||
|
# Disable read access
|
||||||
|
cat <<EOF > $dir/www/data/noread.html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>No read</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
width: 35em;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-family: Tahoma, Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>No read!</h1>
|
||||||
|
<p>If you see this page, you have read access to root</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# remove read access
|
||||||
|
chmod 660 $dir/www/data/noread.html
|
||||||
|
|
||||||
# Http test routine with arguments:
|
# Http test routine with arguments:
|
||||||
# 1. proto:http/https
|
# 1. proto:http/https
|
||||||
function testrun()
|
function testrun()
|
||||||
|
|
@ -134,32 +183,55 @@ EOF
|
||||||
new "wait restconf"
|
new "wait restconf"
|
||||||
wait_restconf $proto
|
wait_restconf $proto
|
||||||
|
|
||||||
# echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html"
|
echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html"
|
||||||
if $enable; then
|
|
||||||
|
if ! $enable; then
|
||||||
|
# XXX or bad request?
|
||||||
|
new "WWW get html, not enabled, expect not found"
|
||||||
|
# echo "curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html"
|
||||||
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 404"
|
||||||
|
else
|
||||||
new "WWW get html"
|
new "WWW get html"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "<title>Welcome to Clixon!</title>"
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" "<title>Welcome to Clixon!</title>"
|
||||||
else
|
|
||||||
new "WWW get html, not enabled, expect bad request"
|
new "WWW get css"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 400"
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html"
|
||||||
return
|
|
||||||
|
new "WWW head"
|
||||||
|
expectpart "$(curl $CURLOPTS --head -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" --not-- "<title>Welcome to Clixon!</title>"
|
||||||
|
|
||||||
|
new "WWW options"
|
||||||
|
expectpart "$(curl $CURLOPTS -X OPTIONS $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "allow: OPTIONS,HEAD,GET"
|
||||||
|
|
||||||
|
# negative errors
|
||||||
|
new "WWW get http not found"
|
||||||
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 404" "Content-Type: text/html" "<title>404 Not Found</title>"
|
||||||
|
|
||||||
|
new "WWW get http soft link"
|
||||||
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/inside.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>" --not-- "<title>Dont access this</title>"
|
||||||
|
|
||||||
|
if [ ! -f /.dockerenv ] ; then # XXX Privs dont not work on docker/alpine?
|
||||||
|
new "WWW get http not read access"
|
||||||
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/noread.html)" 0 "HTTP/$HVER 403" "Content-Type: text/html" "<title>403 Forbidden</title>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try .. Cannot get .. in path to work in curl (it seems to remove it)
|
||||||
|
if [ "$proto" = http -a -n "$netcat" ]; then
|
||||||
|
new "WWW get outside using .. netcat"
|
||||||
|
expectpart "$(${netcat} 127.0.0.1 80 <<EOF
|
||||||
|
GET /data/../../outside.html HTTP/1.1
|
||||||
|
Host: localhost
|
||||||
|
Accept: text_html
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)" 0 "HTTP/1.1 403" "Forbidden"
|
||||||
|
fi
|
||||||
|
|
||||||
|
new "WWW post not allowed"
|
||||||
|
expectpart "$(curl $CURLOPTS -X POST -H 'Accept: text/html' -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 405" "Content-Type: text/html" "<title>405 Method Not Allowed</title>"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "WWW get css"
|
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/example.css)" 0 "HTTP/$HVER 200" "Content-Type: text/css" "display: inline;" --not-- "Content-Type: text/html"
|
|
||||||
|
|
||||||
new "WWW head"
|
|
||||||
expectpart "$(curl $CURLOPTS --head -H 'Accept: text/html' $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "Content-Type: text/html" --not-- "<title>Welcome to Clixon!</title>"
|
|
||||||
|
|
||||||
new "WWW options"
|
|
||||||
expectpart "$(curl $CURLOPTS -X OPTIONS $proto://localhost/data/index.html)" 0 "HTTP/$HVER 200" "allow: OPTIONS,HEAD,GET"
|
|
||||||
|
|
||||||
# negative errors
|
|
||||||
new "WWW get http not found"
|
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: text/html' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 404" "Content-Type: text/html" "<title>404 Not Found</title>"
|
|
||||||
|
|
||||||
new "WWW post not allowed"
|
|
||||||
expectpart "$(curl $CURLOPTS -X POST -H 'Accept: text/html' -H "Content-Type: application/yang-data+json" -d '{"ietf-interfaces:interfaces":{"interface":{"name":"eth/0/0","type":"clixon-example:eth","enabled":true}}}' $proto://localhost/data/notfound.html)" 0 "HTTP/$HVER 405" "Content-Type: text/html" "<title>405 Method Not Allowed</title>"
|
|
||||||
|
|
||||||
if [ $RC -ne 0 ]; then
|
if [ $RC -ne 0 ]; then
|
||||||
new "Kill restconf daemon"
|
new "Kill restconf daemon"
|
||||||
stop_restconf
|
stop_restconf
|
||||||
|
|
@ -190,7 +262,7 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for proto in $protos; do
|
for proto in $protos; do
|
||||||
for enable in true false; do
|
for enable in true false; do # false
|
||||||
new "http-data proto:$proto enabled:$enable"
|
new "http-data proto:$proto enabled:$enable"
|
||||||
testrun $proto $enable
|
testrun $proto $enable
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -209,19 +209,6 @@ expectpart "$(curl $CURLOPTS -X POST -H 'Content-Type: application/yang-data+xml
|
||||||
new "restconf GET initial datastore"
|
new "restconf GET initial datastore"
|
||||||
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 200" "$XML"
|
expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPROTO://localhost/restconf/data/example:a=0)" 0 "HTTP/$HVER 200" "$XML"
|
||||||
|
|
||||||
# XXX cannot get this to work for all combinations of nc/netcat fcgi/native
|
|
||||||
# But leave it here for debugging where netcat works properly
|
|
||||||
# Alt try something like:
|
|
||||||
# printf "Hello World!" | (exec 3<>/dev/tcp/127.0.0.1/80; cat >&3; cat <&3; exec 3<&-)
|
|
||||||
# Look for netcat or nc for direct socket http calls
|
|
||||||
if [ -n "$(type netcat 2> /dev/null)" ]; then
|
|
||||||
netcat="netcat -w 1" # -N does not work on fcgi
|
|
||||||
# nc on freebsd does not work either
|
|
||||||
#elif [ -n "$(type nc 2> /dev/null)" ]; then
|
|
||||||
# netcat=nc
|
|
||||||
else
|
|
||||||
netcat=
|
|
||||||
fi
|
|
||||||
if [ -n "$netcat" -a "${WITH_RESTCONF}" != "fcgi" ]; then
|
if [ -n "$netcat" -a "${WITH_RESTCONF}" != "fcgi" ]; then
|
||||||
|
|
||||||
# new "restconf try fuzz crash"
|
# new "restconf try fuzz crash"
|
||||||
|
|
|
||||||
|
|
@ -605,16 +605,28 @@ module clixon-config {
|
||||||
}
|
}
|
||||||
leaf CLICON_HTTP_DATA_PATH {
|
leaf CLICON_HTTP_DATA_PATH {
|
||||||
if-feature "clrc:http-data";
|
if-feature "clrc:http-data";
|
||||||
|
default "/";
|
||||||
type string;
|
type string;
|
||||||
description
|
description
|
||||||
"If set, enable www data on this sub-path, must start with / (example: /data)";
|
"URI match for http-data serving files specified by CLICON_HTTP_DATA_ROOT.
|
||||||
|
Must start with / (example: /)
|
||||||
|
Restconf paths at /restconf is always done before data (or streams)
|
||||||
|
The PATH is appended to CLICON_HTTP_DATA_ROOT to find a file.
|
||||||
|
Example, if PATH is /data and ROOT is /www, and a GET /index.html, the
|
||||||
|
corresponding file is '/www/data/index.html'
|
||||||
|
Both feature clixon-restconf:http-data and restconf/enable-http-data
|
||||||
|
must be enabled for this match to occur.";
|
||||||
}
|
}
|
||||||
leaf CLICON_HTTP_DATA_ROOT {
|
leaf CLICON_HTTP_DATA_ROOT {
|
||||||
if-feature "clrc:http-data";
|
if-feature "clrc:http-data";
|
||||||
type string;
|
type string;
|
||||||
default "/var/www";
|
default "/var/www";
|
||||||
description
|
description
|
||||||
"public web root";
|
"Location in file system where http-data files are looked for.
|
||||||
|
Soft links, '..', '~' etc are not followed.
|
||||||
|
See also CLICON_HTTP_DATA_PATH
|
||||||
|
Both feature clixon-restconf:http-data and restconf/enable-http-data
|
||||||
|
must be enabled for this match to occur.";
|
||||||
}
|
}
|
||||||
leaf CLICON_CLI_DIR {
|
leaf CLICON_CLI_DIR {
|
||||||
type string;
|
type string;
|
||||||
|
|
@ -1051,8 +1063,9 @@ module clixon-config {
|
||||||
leaf CLICON_STREAM_PATH {
|
leaf CLICON_STREAM_PATH {
|
||||||
type string;
|
type string;
|
||||||
default "streams";
|
default "streams";
|
||||||
description "Stream path appended to CLICON_STREAM_URL to form
|
description
|
||||||
stream subscription URL.";
|
"Stream path appended to CLICON_STREAM_URL to form
|
||||||
|
stream subscription URL.";
|
||||||
}
|
}
|
||||||
leaf CLICON_STREAM_URL {
|
leaf CLICON_STREAM_URL {
|
||||||
type string;
|
type string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue