* Restconf FCGI (eg via nginx) have changed reply message syntax slightly

* native http: new restconf_err files, generic data input, restconf_methods generalized.
* test: expecteq removed.
This commit is contained in:
Olof hagsand 2020-06-16 21:51:00 +02:00
parent c18c40434f
commit 6e714beea5
37 changed files with 1619 additions and 1128 deletions

View file

@ -85,15 +85,15 @@ APPL = clixon_restconf
APPSRC =
APPSRC += restconf_api.c # maybe empty
APPSRC += restconf_api_$(with_restconf).c # cant be .so since libevhtp is a.
APPSRC += restconf_err.c
APPSRC += restconf_root.c
APPSRC += restconf_$(with_restconf)_main.c
APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
# Fcgi-specific source including main
ifeq ($(with_restconf),fcgi)
APPSRC += restconf_fcgi_lib.c # Most of these should be made generic
APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
APPSRC += restconf_stream.c
endif

View file

@ -41,14 +41,14 @@
/*
* Prototypes
*/
int restconf_reply_status_code(void *req, int code);
#if defined(__GNUC__) && __GNUC__ >= 3
int restconf_reply_header_add(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4)));
int restconf_reply_header(void *req, char *name, char *vfmt, ...) __attribute__ ((format (printf, 3, 4)));
#else
int restconf_reply_header_add(FCGX_Request *req, char *name, char *vfmt, ...);
int restconf_reply_header(FCGX_Request *req, char *name, char *vfmt, ...);
#endif
int restconf_reply_send(void *req, cbuf *cb);
int restconf_reply_send(void *req, int code, cbuf *cb);
cbuf *restconf_get_indata(void *req);
#endif /* _RESTCONF_API_H_ */

View file

@ -65,22 +65,6 @@
#include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
req->status = code;
return 0;
}
/*! Add HTTP header field name and value to reply, evhtp specific
* @param[in] req Evhtp http request handle
* @param[in] name HTTP header field name
@ -88,10 +72,10 @@ restconf_reply_status_code(void *req0,
* @see eg RFC 7230
*/
int
restconf_reply_header_add(void *req0,
char *name,
char *vfmt,
...)
restconf_reply_header(void *req0,
char *name,
char *vfmt,
...)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
@ -142,13 +126,20 @@ restconf_reply_header_add(void *req0,
*/
int
restconf_reply_send(void *req0,
int code,
cbuf *cb)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
evhtp_connection_t *conn;
struct evbuffer *eb = NULL;
const char *reason_phrase;
req->status = code;
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0)
goto done;
#if 1 /* Optional? */
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
@ -181,3 +172,20 @@ restconf_reply_send(void *req0,
evhtp_safe_free(eb, evbuffer_free);
return retval;
}
/*! get input data
* @param[in] req Fastcgi request handle
* @note Pulls up an event buffer and then copies it to a cbuf. This is not efficient.
*/
cbuf *
restconf_get_indata(void *req0)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
return NULL;
if (evbuffer_get_length(req->buffer_in))
cprintf(cb, "%s", evbuffer_pullup(req->buffer_in, -1));
return cb;
}

View file

@ -63,20 +63,6 @@
#include "restconf_lib.h"
#include "restconf_api.h" /* Virtual api */
/*! Add HTTP header field name and value to reply, fcgi specific
* @param[in] req Fastcgi request handle
* @param[in] code HTTP status code
* @see eg RFC 7230
*/
int
restconf_reply_status_code(void *req0,
int code)
{
FCGX_Request *req = (FCGX_Request *)req0;
FCGX_SetExitStatus(code, req->out);
return 0;
}
/*! HTTP headers done, if there is a message body coming next
* @param[in] req Fastcgi request handle
@ -102,11 +88,10 @@ restconf_reply_body_start(void *req0)
* @see eg RFC 7230
*/
int
restconf_reply_header_add(void *req0,
char *name,
char *vfmt,
...)
restconf_reply_header(void *req0,
char *name,
char *vfmt,
...)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
@ -195,23 +180,50 @@ restconf_reply_body_add(void *req0,
}
/*! Send HTTP reply with potential message body
* @param[in] req Fastcgi request handle
* @param[in] cb Body as a cbuf, send if
* @param[in] req Fastcgi request handle
* @param[in] code Status code
* @param[in] cb Body as a cbuf if non-NULL
*
* Prerequisites: status code set, headers given, body if wanted set
*/
int
restconf_reply_send(void *req0,
int code,
cbuf *cb)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
const char *reason_phrase;
FCGX_SetExitStatus(code, req->out);
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0)
goto done;
FCGX_FPrintF(req->out, "\r\n");
/* Write a body if cbuf is nonzero */
if (cb != NULL && cbuf_len(cb)){
FCGX_FPrintF(req->out, "\r\n");
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "\r\n");
}
retval = 0;
return retval;
done:
return retval;
}
/*!
* @param[in] req Fastcgi request handle
*/
cbuf *
restconf_get_indata(void *req0)
{
FCGX_Request *req = (FCGX_Request *)req0;
int c;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
return NULL;
while ((c = FCGX_GetChar(req->in)) != -1)
cprintf(cb, "%c", c);
return cb;
}

View file

@ -0,0 +1,495 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 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 *****
*
* Return errors
* @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
/*
* Constants
*/
/* In the fcgi implementations some errors had body, it would be cleaner to skip them
* None seem mandatory according to RFC 7231
*/
#define SKIP_BODY
/*
* NOTE, fcgi seems not enough with a status code (libevhtp is) but must also have a status
* header.
*/
/*! HTTP error 400
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_badrequest(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 400, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s or data is in some way badly formed.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 400, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 401
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_unauthorized(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 400, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "<error-tag>access-denied</error-tag>\n");
cprintf(cb, "The requested URL %s was unauthorized.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 400, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 403
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_forbidden(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 403, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s was forbidden.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 403, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 404
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
* XXX skip body?
*/
int
restconf_notfound(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 404, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The requested URL %s was not found on this server.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 404, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 405
* @param[in] req Generic Www handle
* @param[in] allow Which methods are allowed
*/
int
restconf_method_notallowed(void *req,
char *allow)
{
int retval = -1;
if (restconf_reply_header(req, "Allow", "%s", allow) < 0)
goto done;
if (restconf_reply_send(req, 405, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 406 Not acceptable
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_notacceptable(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 406, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "The target resource does not have a current representation that would be acceptable to the user agent.\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 406, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 409
* @param[in] req Generic Www handle
*/
int
restconf_conflict(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 409, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 409 Unsupporte dmedia
* @param[in] req Generic Www handle
*/
int
restconf_unsupported_media(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 415, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! HTTP error 500 Internal server error
* @param[in] h Clicon handle
* @param[in] req Generic Www handle
*/
int
restconf_internal_server_error(clicon_handle h,
void *req)
{
int retval = -1;
#ifdef SKIP_BODY /* Remove the body - should it really be there? */
if (restconf_reply_send(req, 500, NULL) < 0)
goto done;
retval = 0;
done:
#else
char *path;
cbuf *cb = NULL;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
path = clixon_restconf_param_get("REQUEST_URI", r->envp);
if (restconf_reply_header(req, "Content-Type", "text/html") < 0)
goto done;
cprintf(cb, "Internal server error when accessing %s</h1>\n", path);
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)=
goto done;
if (restconf_reply_send(req, 500, cb) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
#endif
return retval;
}
/*! HTTP error 501 Not implemented
* @param[in] req Generic Www handle
*/
int
restconf_notimplemented(void *req)
{
int retval = -1;
if (restconf_reply_send(req, 501, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Generic restconf error function on get/head request
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] xerr XML error message from backend
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media Output media
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
* otherwise use this code
*/
int
api_return_err(clicon_handle h,
void *req,
cxobj *xerr,
int pretty,
restconf_media media,
int code0)
{
int retval = -1;
cbuf *cb = NULL;
cbuf *cberr = NULL;
cxobj *xtag;
char *tagstr;
int code;
cxobj *xerr2 = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* A well-formed error message when entering here should look like:
* <rpc-error>...<error-tag>invalid-value</error-tag>
* Check this is so, otherwise generate an internal error.
*/
if (strcmp(xml_name(xerr), "rpc-error") != 0 ||
(xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Internal error, system returned invalid error message: ");
if (netconf_err2cb(xerr, cberr) < 0)
goto done;
if (netconf_operation_failed_xml(&xerr2, "application",
cbuf_get(cberr)) < 0)
goto done;
if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){
clicon_err(OE_XML, 0, "Internal error, shouldnt happen");
goto done;
}
}
if (xml_name_set(xerr, "error") < 0)
goto done;
tagstr = xml_body(xtag);
if (code0 != 0)
code = code0;
else{
if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
}
if (restconf_reply_header(req, "Content_Type", "%s", restconf_media_int2str(media)) < 0)
goto done;
switch (media){
case YANG_DATA_XML:
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
cprintf(cb, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n");
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
cprintf(cb, " </errors>\r\n");
}
else {
cprintf(cb, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">");
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
cprintf(cb, "</errors>\r\n");
}
break;
case YANG_DATA_JSON:
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
cprintf(cb, "{\n\"ietf-restconf:errors\" : ");
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
cprintf(cb, "\n}\r\n");
}
else{
cprintf(cb, "{");
cprintf(cb, "\"ietf-restconf:errors\":");
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
cprintf(cb, "}\r\n");
}
break;
default:
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
goto done;
break;
} /* switch media */
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, code, cb) < 0)
goto done;
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (cberr)
cbuf_free(cberr);
return retval;
}

View file

@ -31,30 +31,30 @@
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Return errors
* @see RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
*/
#ifndef _RESTCONF_FCGI_LIB_H_
#define _RESTCONF_FCGI_LIB_H_
#ifndef _RESTCONF_ERR_H_
#define _RESTCONF_ERR_H_
/*
* Prototypes
*/
int restconf_badrequest(clicon_handle h, FCGX_Request *r);
int restconf_unauthorized(clicon_handle h, FCGX_Request *r);
int restconf_forbidden(clicon_handle h, FCGX_Request *r);
int restconf_notfound(clicon_handle h, FCGX_Request *r);
int restconf_notacceptable(clicon_handle h, FCGX_Request *r);
int restconf_conflict(FCGX_Request *r);
int restconf_unsupported_media(FCGX_Request *r);
int restconf_internal_server_error(clicon_handle h, FCGX_Request *r);
int restconf_notimplemented(FCGX_Request *r);
int restconf_test(FCGX_Request *r, int dbg);
int clixon_restconf_params_set(clicon_handle h,
char **envp);
int clixon_restconf_params_clear(clicon_handle h, char **envp);
cbuf *readdata(FCGX_Request *r);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr, int pretty, restconf_media media, int code0);
int http_location(clicon_handle h, FCGX_Request *r, cxobj *xobj);
#endif /* _RESTCONF_FCGI_LIB_H_ */
int restconf_badrequest(clicon_handle h, void *req);
int restconf_unauthorized(clicon_handle h, void *req);
int restconf_forbidden(clicon_handle h, void *req);
int restconf_notfound(clicon_handle h, void *req);
int restconf_method_notallowed(void *req, char *allow);
int restconf_notacceptable(clicon_handle h, void *req);
int restconf_conflict(void *req);
int restconf_unsupported_media(void *req);
int restconf_internal_server_error(clicon_handle h, void *req);
int restconf_notimplemented(void *req);
int api_return_err(clicon_handle h, void *req, cxobj *xerr, int pretty, restconf_media media, int code0);
#endif /* _RESTCONF_ERR_H_ */

View file

@ -178,6 +178,7 @@ query_iterator(evhtp_header_t *hdr,
* several different places in evhtp
* @param[in] h Clicon handle
* @param[in] req Evhtp request struct
* @param[out] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @retval 0 OK
* @retval -1 Error
* The following parameters are set:
@ -211,7 +212,10 @@ evhtp_params_set(clicon_handle h,
}
meth = evhtp_request_get_method(req);
/* QUERY_STRING */
/* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string?
* This is different from all else: Ie one could have re-created a string here but
* that would mean double parsing,...
*/
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
@ -349,70 +353,29 @@ static void
cx_path_restconf(evhtp_request_t *req,
void *arg)
{
evhtp_connection_t *conn;
clicon_handle h = arg;
struct evbuffer *b = NULL;
cvec *qvec = NULL;
size_t len = 0;
cbuf *cblen = NULL;
clicon_handle h = arg;
cvec *qvec = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
if ((cblen = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* get accepted connection */
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
/* Get all parameters from this request (resembling fcgi) */
/* set fcgi-like paramaters (ignore query vector) */
if (evhtp_params_set(h, req, qvec) < 0)
goto done;
/* 1. create body */
if ((b = evbuffer_new()) == NULL){
clicon_err(OE_DAEMON, errno, "evbuffer_new");
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
}
cprintf(cblen, "%lu", len);
/* 2. add headers (can mix with body) */
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Cache-Control", "no-cache", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/xrd+xml", 0, 0));
evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Length", cbuf_get(cblen), 0, 0));
/* Optional? */
htp_sslutil_add_xheaders(req->headers_out, conn->ssl, HTP_SSLUTILS_XHDR_ALL);
/* 3. send reply */
evhtp_send_reply_start(req, EVHTP_RES_OK);
evhtp_send_reply_body(req, b);
evhtp_send_reply_end(req);
/* Clear (fcgi)paramaters */
/* Clear (fcgi) paramaters from this request */
if (evhtp_params_clear(h) < 0)
goto done;
done:
if (qvec)
cvec_free(qvec);
if (cblen)
cbuf_free(cblen);
if (b)
evhtp_safe_free(b, evbuffer_free);
return; /* void */
}
@ -756,7 +719,6 @@ main(int argc,
if (clicon_options_main(h) < 0)
goto done;
event_base_loop(evbase, 0);
evhtp_unbind_socket(htp);

View file

@ -1,480 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2020 Olof Hagsand
Copyright (C) 2020 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 *****
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
* @note The response payload for errors uses text_html. RFC7231 is vague
* on the response payload (and its media). Maybe it should be omitted
* altogether?
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <dlfcn.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/wait.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
/*! HTTP error 400
* @param[in] req Fastcgi request handle
*/
int
restconf_badrequest(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(400, req->out);
FCGX_FPrintF(req->out, "Status: 400 Bad Request\r\n"); /* 400 bad request */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Clixon Bad request/h1>\n");
FCGX_FPrintF(req->out, "The requested URL %s or data is in some way badly formed.\n",
path);
return 0;
}
/*! HTTP error 401
* @param[in] req Fastcgi request handle
*/
int
restconf_unauthorized(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(401, req->out);
FCGX_FPrintF(req->out, "Status: 401 Unauthorized\r\n"); /* 401 unauthorized */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<error-tag>access-denied</error-tag>\n");
FCGX_FPrintF(req->out, "The requested URL %s was unauthorized.\n", path);
return 0;
}
/*! HTTP error 403
* @param[in] req Fastcgi request handle
*/
int
restconf_forbidden(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(403, req->out);
FCGX_FPrintF(req->out, "Status: 403 Forbidden\r\n"); /* 403 forbidden */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Forbidden</h1>\n");
FCGX_FPrintF(req->out, "The requested URL %s was forbidden.\n", path);
return 0;
}
/*! HTTP error 404
* @param[in] req Fastcgi request handle
*/
int
restconf_notfound(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(404, req->out);
FCGX_FPrintF(req->out, "Status: 404 Not Found\r\n"); /* 404 not found */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Found</h1>\n");
FCGX_FPrintF(req->out, "Not Found\n");
FCGX_FPrintF(req->out, "The requested URL %s was not found on this server.\n",
path);
return 0;
}
/*! HTTP error 406 Not acceptable
* @param[in] req Fastcgi request handle
*/
int
restconf_notacceptable(clicon_handle h,
FCGX_Request *req)
{
char *path;
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_SetExitStatus(406, req->out);
FCGX_FPrintF(req->out, "Status: 406 Not Acceptable\r\n"); /* 406 not acceptible */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Acceptable</h1>\n");
FCGX_FPrintF(req->out, "Not Acceptable\n");
FCGX_FPrintF(req->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
path);
return 0;
}
/*! HTTP error 409
* @param[in] req Fastcgi request handle
*/
int
restconf_conflict(FCGX_Request *req)
{
FCGX_SetExitStatus(409, req->out);
FCGX_FPrintF(req->out, "Status: 409 Conflict\r\n"); /* 409 Conflict */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Data resource already exists</h1>\n");
return 0;
}
/*! HTTP error 409
* @param[in] req Fastcgi request handle
*/
int
restconf_unsupported_media(FCGX_Request *req)
{
FCGX_SetExitStatus(415, req->out);
FCGX_FPrintF(req->out, "Status: 415 Unsupported Media Type\r\n");
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Unsupported Media Type</h1>\n");
return 0;
}
/*! HTTP error 500
* @param[in] req Fastcgi request handle
*/
int
restconf_internal_server_error(clicon_handle h,
FCGX_Request *req)
{
char *path;
clicon_debug(1, "%s", __FUNCTION__);
path = clixon_restconf_param_get(h, "REQUEST_URI");
FCGX_FPrintF(req->out, "Status: 500 Internal Server Error\r\n"); /* 500 internal server error */
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Internal server error when accessing %s</h1>\n", path);
return 0;
}
/*! HTTP error 501
* @param[in] req Fastcgi request handle
*/
int
restconf_notimplemented(FCGX_Request *req)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_FPrintF(req->out, "Status: 501 Not Implemented\r\n");
FCGX_FPrintF(req->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(req->out, "<h1>Not Implemented/h1>\n");
return 0;
}
/*! Print all FCGI headers
* @param[in] req Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
restconf_test(FCGX_Request *req,
int dbg)
{
char **environ = req->envp;
int i;
clicon_debug(1, "All environment vars:");
for (i = 0; environ[i] != NULL; i++){
clicon_debug(1, "%s", environ[i]);
}
clicon_debug(1, "End environment vars");
return 0;
}
/*! Convert FCGI parameters to clixon runtime data
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
clixon_restconf_params_set(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
char *val = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, &val) < 0)
goto done;
clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val);
if (clixon_restconf_param_set(h, param, val) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
if (val){
free(val);
val = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Clear all FCGI parameters in an environment
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
int
clixon_restconf_params_clear(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, NULL) < 0)
goto done;
clicon_debug(1, "%s param:%s", __FUNCTION__, param);
if (clixon_restconf_param_del(h, param) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*!
* @param[in] req Fastcgi request handle
*/
cbuf *
readdata(FCGX_Request *req)
{
int c;
cbuf *cb;
if ((cb = cbuf_new()) == NULL)
return NULL;
while ((c = FCGX_GetChar(req->in)) != -1)
cprintf(cb, "%c", c);
return cb;
}
/*! Return restconf error on get/head request
* @param[in] h Clixon handle
* @param[in] req Fastcgi request handle
* @param[in] xerr XML error message from backend
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media Output media
* @param[in] code If 0 use rfc8040 sec 7 netconf2restconf error-tag mapping
* otherwise use this code
*/
int
api_return_err(clicon_handle h,
FCGX_Request *req,
cxobj *xerr,
int pretty,
restconf_media media,
int code0)
{
int retval = -1;
cbuf *cb = NULL;
cbuf *cberr = NULL;
cxobj *xtag;
char *tagstr;
int code;
cxobj *xerr2 = NULL;
const char *reason_phrase;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* A well-formed error message when entering here should look like:
* <rpc-error>...<error-tag>invalid-value</error-tag>
* Check this is so, otherwise generate an internal error.
*/
if (strcmp(xml_name(xerr), "rpc-error") != 0 ||
(xtag = xpath_first(xerr, NULL, "error-tag")) == NULL){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "Internal error, system returned invalid error message: ");
if (netconf_err2cb(xerr, cberr) < 0)
goto done;
if (netconf_operation_failed_xml(&xerr2, "application",
cbuf_get(cberr)) < 0)
goto done;
if ((xerr = xpath_first(xerr2, NULL, "//rpc-error")) == NULL){
clicon_err(OE_XML, 0, "Internal error, shouldnt happen");
goto done;
}
}
if (xml_name_set(xerr, "error") < 0)
goto done;
tagstr = xml_body(xtag);
if (code0 != 0)
code = code0;
else{
if ((code = restconf_err2code(tagstr)) < 0)
code = 500; /* internal server error */
}
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
FCGX_SetExitStatus(code, req->out); /* Created */
FCGX_FPrintF(req->out, "Status: %d %s\r\n", code, reason_phrase);
FCGX_FPrintF(req->out, "Content-Type: %s\r\n\r\n", restconf_media_int2str(media));
switch (media){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xerr, 2, pretty, -1) < 0)
goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
FCGX_FPrintF(req->out, " <errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">\n", cbuf_get(cb));
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, " </errors>\r\n");
}
else {
FCGX_FPrintF(req->out, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">", cbuf_get(cb));
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "</errors>\r\n");
}
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xerr, pretty) < 0)
goto done;
clicon_debug(1, "%s code:%d err:%s", __FUNCTION__, code, cbuf_get(cb));
if (pretty){
FCGX_FPrintF(req->out, "{\n");
FCGX_FPrintF(req->out, " \"ietf-restconf:errors\" : %s\n",
cbuf_get(cb));
FCGX_FPrintF(req->out, "}\r\n");
}
else{
FCGX_FPrintF(req->out, "{");
FCGX_FPrintF(req->out, "\"ietf-restconf:errors\":");
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "}\r\n");
}
break;
default:
clicon_err(OE_YANG, EINVAL, "Invalid media type %d", media);
goto done;
break;
} /* switch media */
// ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (cberr)
cbuf_free(cberr);
return retval;
}
/*! Print location header from FCGI environment
* @param[in] req Fastcgi request handle
* @param[in] xobj If set (eg POST) add to api-path
* $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored
*/
int
http_location(clicon_handle h,
FCGX_Request *req,
cxobj *xobj)
{
int retval = -1;
char *https;
char *host;
char *request_uri;
cbuf *cb = NULL;
https = clixon_restconf_param_get(h, "HTTPS");
host = clixon_restconf_param_get(h, "HTTP_HOST");
request_uri = clixon_restconf_param_get(h, "REQUEST_URI");
if (xobj != NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
if (xml2api_path_1(xobj, cb) < 0)
goto done;
FCGX_FPrintF(req->out, "Location: http%s://%s%s%s\r\n",
https?"s":"",
host,
request_uri,
cbuf_get(cb));
}
else
FCGX_FPrintF(req->out, "Location: http%s://%s%s\r\n",
https?"s":"",
host,
request_uri);
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -79,9 +79,8 @@
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_err.h"
#include "restconf_root.h" /* generic not shared with plugins */
#include "restconf_fcgi_lib.h" /* fcgi specific */
#include "restconf_methods.h" /* fcgi specific */
#include "restconf_methods_get.h"
#include "restconf_methods_post.h"
@ -90,6 +89,90 @@
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:"
/*! Convert FCGI parameters to clixon runtime data
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_params_set(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
char *val = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, &val) < 0)
goto done;
clicon_debug(1, "%s param:%s val:%s", __FUNCTION__, param, val);
if (clixon_restconf_param_set(h, param, val) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
if (val){
free(val);
val = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Clear all FCGI parameters in an environment
* @param[in] h Clixon handle
* @param[in] envp Fastcgi request handle parameter array on the format "<param>=<value>"
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_params_clear(clicon_handle h,
char **envp)
{
int retval = -1;
int i;
char *param = NULL;
clicon_debug(1, "%s", __FUNCTION__);
for (i = 0; envp[i] != NULL; i++){ /* on the form <param>=<value> */
if (clixon_strsplit(envp[i], '=', &param, NULL) < 0)
goto done;
clicon_debug(1, "%s param:%s", __FUNCTION__, param);
if (clixon_restconf_param_del(h, param) < 0)
goto done;
if (param){
free(param);
param = NULL;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
/*! Print all FCGI headers
* @param[in] req Fastcgi request handle
* @see https://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
*/
static int
fcgi_print_headers(FCGX_Request *req)
{
char **environ = req->envp;
int i;
clicon_debug(1, "All environment vars:");
for (i = 0; environ[i] != NULL; i++)
clicon_debug(1, "%s", environ[i]);
clicon_debug(1, "End environment vars");
return 0;
}
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@ -97,7 +180,6 @@
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input daat
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_in Input media
* @param[in] media_out Output media
@ -176,46 +258,6 @@ api_operations(clicon_handle h,
return retval;
}
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Hardcoded to "/restconf"
* Return see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
*/
#if 0
static int
api_well_known(clicon_handle h,
FCGX_Request *req)
{
char *request_method;
FCGX_Request *body;
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
if (strcmp(request_method, "GET") !=0 )
return restconf_method_notallowed(req, "GET");
restconf_reply_status_code(req, 200); /* OK */
restconf_reply_header_add(req, "Cache-Control", "no-cache");
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml");
body = restconf_reply_body_start(req);
restconf_reply_body_add(body, NULL, "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>\n");
restconf_reply_body_add(body, NULL, " <Link rel='restconf' href='/restconf'/>\n");
restconf_reply_body_add(body, NULL, "</XRD>\r\n");
done:
return 0;
}
#endif
/*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
@ -224,10 +266,10 @@ api_well_known(clicon_handle h,
* XXX doesnt check method
*/
static int
api_root(clicon_handle h,
FCGX_Request *r,
int pretty,
restconf_media media_out)
api_fcgi_root(clicon_handle h,
FCGX_Request *r,
int pretty,
restconf_media media_out)
{
int retval = -1;
@ -337,25 +379,23 @@ api_yang_library_version(clicon_handle h,
* @param[in] r Fastcgi request handle
*/
static int
api_restconf(clicon_handle h,
FCGX_Request *req)
api_fcgi_restconf(clicon_handle h,
FCGX_Request *req)
{
int retval = -1;
char *path;
char *query = NULL;
char *method;
char *api_resource;
char **pvec = NULL;
int pn;
cvec *qvec = NULL;
cvec *dvec = NULL;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *data;
char *indata = NULL;
int authenticated = 0;
char *media_str = NULL;
restconf_media media_out = YANG_DATA_JSON;
int pretty;
cbuf *cbret = NULL;
cxobj *xret = NULL;
cxobj *xerr;
@ -398,30 +438,28 @@ api_restconf(clicon_handle h,
retval = restconf_notfound(h, req);
goto done;
}
restconf_test(req, 1);
fcgi_print_headers(req);
if (pn == 2){
retval = api_root(h, req, pretty, media_out);
retval = api_fcgi_root(h, req, pretty, media_out);
goto done;
}
if ((method = pvec[2]) == NULL){
if ((api_resource = pvec[2]) == NULL){
retval = restconf_notfound(h, req);
goto done;
}
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource);
if (query != NULL && strlen(query))
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(req)) == NULL)
/* indata */
if ((cb = restconf_get_indata(req)) == NULL)
goto done;
data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
indata = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
@ -429,7 +467,7 @@ api_restconf(clicon_handle h,
goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, we set a dummy user */
/* If set but no user, set a dummy user */
if (authenticated){
if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none");
@ -445,22 +483,20 @@ api_restconf(clicon_handle h,
goto ok;
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (strcmp(method, "yang-library-version")==0){
if (strcmp(api_resource, "yang-library-version")==0){
if (api_yang_library_version(h, req, pretty, media_out) < 0)
goto done;
}
else if (strcmp(method, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, req, path, pcvec, 2, qvec, data,
else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */
if (api_data(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else if (strcmp(method, "operations") == 0){ /* rpc */
if (api_operations(h, req, path, pcvec, 2, qvec, data,
else if (strcmp(api_resource, "operations") == 0){ /* rpc */
if (api_operations(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else if (strcmp(method, "test") == 0)
restconf_test(req, 0);
else
restconf_notfound(h, req);
ok:
@ -469,16 +505,12 @@ api_restconf(clicon_handle h,
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec)
free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (xret)
xml_free(xret);
return retval;
@ -834,12 +866,16 @@ main(int argc,
/* Translate from FCGI parameter form to Clixon runtime data
* XXX: potential name collision?
*/
if (clixon_restconf_params_set(h, req->envp) < 0)
if (fcgi_params_set(h, req->envp) < 0)
goto done;
/* Debug_ print headers */
if (clicon_debug_get())
fcgi_print_headers(req);
if ((path = clixon_restconf_param_get(h, "REQUEST_URI")) != NULL){
clicon_debug(1, "path: %s", path);
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, req); /* This is the function */
api_fcgi_restconf(h, req); /* This is the function */
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
api_stream(h, req, stream_path, &finish);
}
@ -853,7 +889,7 @@ main(int argc,
}
else
clicon_debug(1, "NULL URI");
if (clixon_restconf_params_clear(h, req->envp) < 0)
if (fcgi_params_clear(h, req->envp) < 0)
goto done;
if (finish)
FCGX_Finish_r(req);

View file

@ -478,7 +478,6 @@ restconf_uripath(clicon_handle h)
return path;
}
/*! Drop privileges from root to user (or already at user)
* @param[in] h Clicon handle
* @param[in] user Drop to this level
@ -540,15 +539,3 @@ restconf_drop_privileges(clicon_handle h,
return retval;
}
/*! HTTP error 405
* @param[in] req Generic Www handle
* @param[in] allow Which methods are allowed
*/
int
restconf_method_notallowed(void *req,
char *allow)
{
restconf_reply_status_code(req, 405);
restconf_reply_header_add(req, "Allow", "%s", allow);
return 0;
}

View file

@ -69,6 +69,5 @@ int clixon_restconf_param_set(clicon_handle h, char *param, char *val);
int clixon_restconf_param_del(clicon_handle h, char *param);
char *restconf_uripath(clicon_handle h);
int restconf_drop_privileges(clicon_handle h, char *user);
int restconf_method_notallowed(void *req, char *allow);
#endif /* _RESTCONF_LIB_H_ */

View file

@ -119,16 +119,16 @@ Mapping netconf error-tag -> status code
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods.h"
/*! REST OPTIONS method
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] req Generic Www handle
*
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
@ -139,14 +139,20 @@ Mapping netconf error-tag -> status code
*/
int
api_data_options(clicon_handle h,
FCGX_Request *r)
void *req)
{
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Allow: OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE\r\n");
FCGX_FPrintF(r->out, "Accept-Patch: application/yang-data+xml,application/yang-data+json\r\n");
FCGX_FPrintF(r->out, "\r\n");
return 0;
if (restconf_reply_header(req, "Allow", "OPTIONS,HEAD,GET,POST,PUT,PATCH,DELETE") < 0)
goto done;
if (restconf_reply_header(req, "Accept-Patch", "application/yang-data+xml,application/yang-data+json") < 0)
goto done;
if (restconf_reply_send(req, 200, NULL) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Check matching keys
@ -226,7 +232,7 @@ match_list_keys(yang_stmt *y,
*/
static int
api_data_write(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
@ -285,7 +291,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -300,7 +306,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
@ -311,7 +317,7 @@ api_data_write(clicon_handle h,
#endif
if (xml_child_nr(xret) == 0){ /* Object does not exist */
if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */
restconf_badrequest(h, r);
restconf_badrequest(h, req);
goto ok;
}
else
@ -340,7 +346,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -357,7 +363,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -400,7 +406,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -409,7 +415,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -422,7 +428,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -431,13 +437,13 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
break;
default:
restconf_unsupported_media(r);
restconf_unsupported_media(req);
goto ok;
break;
} /* switch media_in */
@ -452,7 +458,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -471,7 +477,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -520,7 +526,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -545,7 +551,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -569,7 +575,7 @@ api_data_write(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -610,7 +616,7 @@ api_data_write(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -631,7 +637,7 @@ api_data_write(clicon_handle h,
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -659,14 +665,13 @@ api_data_write(clicon_handle h,
}
/* Check if it was created, or if we tried again and replaced it */
if (op == OP_CREATE){
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Status: 201 Created\r\n");
if (restconf_reply_send(req, 201, NULL) < 0)
goto done;
}
else{
FCGX_SetExitStatus(204, r->out); /* Replaced */
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
if (restconf_reply_send(req, 204, NULL) < 0)
goto done;
}
FCGX_FPrintF(r->out, "\r\n");
ok:
retval = 0;
done:
@ -693,14 +698,14 @@ api_data_write(clicon_handle h,
} /* api_data_write */
/*! Generic REST PUT method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @note restconf PUT is mapped to edit-config replace.
@ -729,7 +734,7 @@ api_data_write(clicon_handle h,
*/
int
api_data_put(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
@ -741,19 +746,19 @@ api_data_put(clicon_handle h,
restconf_media media_in;
media_in = restconf_content_type(h);
return api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
return api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 0);
}
/*! Generic REST PATCH method for plain patch
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* Netconf: <edit-config> (nc:operation="merge")
* See RFC8040 Sec 4.6.1
@ -764,7 +769,7 @@ api_data_put(clicon_handle h,
*/
int
api_data_patch(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path0,
cvec *pcvec,
int pi,
@ -780,26 +785,26 @@ api_data_patch(clicon_handle h,
switch (media_in){
case YANG_DATA_XML:
case YANG_DATA_JSON: /* plain patch */
ret = api_data_write(h, r, api_path0, pcvec, pi, qvec, data, pretty,
ret = api_data_write(h, req, api_path0, pcvec, pi, qvec, data, pretty,
media_in, media_out, 1);
break;
case YANG_PATCH_XML:
case YANG_PATCH_JSON: /* RFC 8072 patch */
ret = restconf_notimplemented(r);
ret = restconf_notimplemented(req);
break;
default:
ret = restconf_unsupported_media(r);
ret = restconf_unsupported_media(req);
break;
}
return ret;
}
/*! Generic REST DELETE method translated to edit-config
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pi Offset, where path starts
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pi Offset, where path starts
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* See RFC 8040 Sec 4.7
* Example:
@ -808,7 +813,7 @@ api_data_patch(clicon_handle h,
*/
int
api_data_delete(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
int pi,
int pretty,
@ -850,7 +855,7 @@ api_data_delete(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -879,7 +884,7 @@ api_data_delete(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -901,7 +906,7 @@ api_data_delete(clicon_handle h,
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -927,10 +932,8 @@ api_data_delete(clicon_handle h,
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
FCGX_SetExitStatus(204, r->out);
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
if (restconf_reply_send(req, 204, NULL) < 0)
goto done;
ok:
retval = 0;
done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
@ -41,18 +42,18 @@
/*
* Prototypes
*/
int api_data_options(clicon_handle h, FCGX_Request *r);
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
int api_data_options(clicon_handle h, void *req);
int api_data_put(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data,
int pretty, restconf_media media_out);
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
int api_data_patch(clicon_handle h, void *req, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data, int pretty,
restconf_media media_out);
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi,
int api_data_delete(clicon_handle h, void *req, char *api_path, int pi,
int pretty, restconf_media media_out);
#endif /* _RESTCONF_METHODS_H_ */

View file

@ -57,21 +57,20 @@
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods_get.h"
/*! Generic GET (both HEAD and GET)
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @param[in] head If 1 is HEAD, otherwise GET
* @code
@ -93,7 +92,7 @@
*/
static int
api_data_get2(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
cvec *pcvec, /* XXX remove? */
int pi,
@ -148,7 +147,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -165,7 +164,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -187,7 +186,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -213,7 +212,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -226,7 +225,7 @@ api_data_get2(clicon_handle h,
#endif
/* Check if error return */
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -234,9 +233,13 @@ api_data_get2(clicon_handle h,
if ((cbx = cbuf_new()) == NULL)
goto done;
if (head){
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
/* Same headers as the GET, but no body */
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (restconf_reply_send(req, 200, NULL) < 0)
goto done;
goto ok;
}
if (xpath==NULL || strcmp(xpath,"/")==0){ /* Special case: data root */
@ -261,7 +264,7 @@ api_data_get2(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -275,7 +278,7 @@ api_data_get2(clicon_handle h,
goto done;
/* override invalid-value default 400 with 404 */
if ((xe = xpath_first(xerr, NULL, "rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 404) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 404) < 0)
goto done;
}
goto ok;
@ -309,12 +312,12 @@ api_data_get2(clicon_handle h,
}
}
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_send(req, 200, cbx) < 0)
goto done;
ok:
retval = 0;
done:
@ -337,13 +340,13 @@ api_data_get2(clicon_handle h,
}
/*! REST HEAD method
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
*
* The HEAD method is sent by the client to retrieve just the header fields
@ -353,7 +356,7 @@ api_data_get2(clicon_handle h,
*/
int
api_data_head(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
cvec *pcvec,
int pi,
@ -361,18 +364,18 @@ api_data_head(clicon_handle h,
int pretty,
restconf_media media_out)
{
return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 1);
return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 1);
}
/*! REST GET method
* According to restconf
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
@ -392,7 +395,7 @@ api_data_head(clicon_handle h,
*/
int
api_data_get(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
cvec *pcvec,
int pi,
@ -400,12 +403,12 @@ api_data_get(clicon_handle h,
int pretty,
restconf_media media_out)
{
return api_data_get2(h, r, api_path, pcvec, pi, qvec, pretty, media_out, 0);
return api_data_get2(h, req, api_path, pcvec, pi, qvec, pretty, media_out, 0);
}
/*! GET restconf/operations resource
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] req Generic Www handle
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
@ -430,7 +433,7 @@ api_data_get(clicon_handle h,
*/
int
api_operations_get(clicon_handle h,
FCGX_Request *r,
void *req,
char *path,
int pi,
cvec *qvec,
@ -505,11 +508,13 @@ api_operations_get(clicon_handle h,
default:
break;
}
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_send(req, 200, cbx) < 0)
goto done;
// ok:
retval = 0;
done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
@ -40,11 +41,11 @@
/*
* Prototypes
*/
int api_data_head(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi,
int api_data_head(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out);
int api_data_get(clicon_handle h, FCGX_Request *r, char *api_path, cvec *pcvec, int pi,
int api_data_get(clicon_handle h, void *req, char *api_path, cvec *pcvec, int pi,
cvec *qvec, int pretty, restconf_media media_out);
int api_operations_get(clicon_handle h, FCGX_Request *r,
int api_operations_get(clicon_handle h, void *req,
char *api_path, int pi, cvec *qvec, char *data,
int pretty, restconf_media media_out);

View file

@ -59,21 +59,67 @@
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_methods_post.h"
/*! Print location header from
* @param[in] req Generic Www handle
* @param[in] xobj If set (eg POST) add to api-path
* $https on if connection operates in SSL mode, or an empty string otherwise
* @note ports are ignored
*/
static int
http_location_header(clicon_handle h,
void *req,
cxobj *xobj)
{
int retval = -1;
char *https;
char *host;
char *request_uri;
cbuf *cb = NULL;
https = clixon_restconf_param_get(h, "HTTPS");
host = clixon_restconf_param_get(h, "HTTP_HOST");
request_uri = clixon_restconf_param_get(h, "REQUEST_URI");
if (xobj != NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
goto done;
}
if (xml2api_path_1(xobj, cb) < 0)
goto done;
if (restconf_reply_header(req, "Location", "http%s://%s%s%s",
https?"s":"",
host,
request_uri,
cbuf_get(cb)) < 0)
goto done;
}
else
if (restconf_reply_header(req, "Location", "http%s://%s%s",
https?"s":"",
host,
request_uri) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Generic REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* restconf POST is mapped to edit-config create.
* @see RFC8040 Sec 4.4.1
@ -99,7 +145,7 @@
*/
int
api_data_post(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
int pi,
cvec *qvec,
@ -153,7 +199,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -168,7 +214,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -198,7 +244,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -207,7 +253,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -220,7 +266,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -229,13 +275,13 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
break;
default:
restconf_unsupported_media(r);
restconf_unsupported_media(req);
goto ok;
break;
} /* switch media_in */
@ -251,7 +297,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -284,7 +330,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
@ -298,7 +344,7 @@ api_data_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -332,7 +378,7 @@ api_data_post(clicon_handle h,
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -354,7 +400,7 @@ api_data_post(clicon_handle h,
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0) /* Use original xe */
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) /* Use original xe */
goto done;
goto ok;
}
@ -376,14 +422,13 @@ api_data_post(clicon_handle h,
goto done;
/* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Status: 201 Created\r\n");
http_location(h, r, xdata);
FCGX_FPrintF(r->out, "\r\n");
if (http_location_header(h, req, xdata) < 0)
goto done;
if (restconf_reply_send(req, 201, NULL) < 0)
goto done;
ok:
retval = 0;
done:
@ -404,8 +449,8 @@ api_data_post(clicon_handle h,
} /* api_data_post */
/*! Handle input data to api_operations_post
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] data Stream input data
* @param[in] yspec Yang top-level specification
* @param[in] yrpc Yang rpc spec
@ -426,7 +471,7 @@ api_data_post(clicon_handle h,
*/
static int
api_operations_post_input(clicon_handle h,
FCGX_Request *r,
void *req,
char *data,
yang_stmt *yspec,
yang_stmt *yrpc,
@ -462,17 +507,16 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
if (ret == 0){
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
clicon_debug(1, "%s F", __FUNCTION__);
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
@ -487,7 +531,7 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
@ -496,17 +540,16 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
break;
default:
restconf_unsupported_media(r);
restconf_unsupported_media(req);
goto fail;
break;
} /* switch media_in */
clicon_debug(1, "%s F", __FUNCTION__);
xml_name_set(xdata, "data");
/* Here xdata is:
* <data><input xmlns="urn:example:clixon">...</input></data>
@ -531,7 +574,7 @@ api_operations_post_input(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
@ -560,8 +603,8 @@ api_operations_post_input(clicon_handle h,
}
/*! Handle output data to api_operations_post
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] xret XML reply messages from backend/handler
* @param[in] yspec Yang top-level specification
* @param[in] youtput Yang rpc output specification
@ -575,7 +618,7 @@ api_operations_post_input(clicon_handle h,
*/
static int
api_operations_post_output(clicon_handle h,
FCGX_Request *r,
void *req,
cxobj *xret,
yang_stmt *yspec,
yang_stmt *youtput,
@ -605,7 +648,7 @@ api_operations_post_output(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
@ -638,7 +681,7 @@ api_operations_post_output(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto fail;
}
@ -659,9 +702,8 @@ api_operations_post_output(clicon_handle h,
strcmp(xml_name(xok),"ok")==0);
if (isempty) {
/* Internal error - invalid output from rpc handler */
FCGX_SetExitStatus(204, r->out); /* OK */
FCGX_FPrintF(r->out, "Status: 204 No Content\r\n");
FCGX_FPrintF(r->out, "\r\n");
if (restconf_reply_send(req, 204, NULL) < 0)
goto done;
goto fail;
}
/* Clear namespace of parameters */
@ -687,12 +729,12 @@ api_operations_post_output(clicon_handle h,
}
/*! REST operation POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] h Clixon handle
* @param[in] req Generic Www handle
* @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040)
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_out Output media
* See RFC 8040 Sec 3.6 / 4.4.2
* @note We map post to edit-config create.
@ -717,7 +759,7 @@ api_operations_post_output(clicon_handle h,
*/
int
api_operations_post(clicon_handle h,
FCGX_Request *r,
void *req,
char *api_path,
int pi,
cvec *qvec,
@ -766,7 +808,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -785,7 +827,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -796,7 +838,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -821,7 +863,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -834,7 +876,7 @@ api_operations_post(clicon_handle h,
namespace = xml_find_type_value(xbot, NULL, "xmlns", CX_ATTR);
clicon_debug(1, "%s : 4. Parse input data: %s", __FUNCTION__, data);
if (data && strlen(data)){
if ((ret = api_operations_post_input(h, r, data, yspec, yrpc, xbot,
if ((ret = api_operations_post_input(h, req, data, yspec, yrpc, xbot,
pretty, media_out)) < 0)
goto done;
if (ret == 0)
@ -854,7 +896,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -866,7 +908,7 @@ api_operations_post(clicon_handle h,
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto ok;
}
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -884,14 +926,14 @@ api_operations_post(clicon_handle h,
/* Look for local (client-side) restconf plugins.
* -1:Error, 0:OK local, 1:OK backend
*/
if ((ret = rpc_callback_call(h, xbot, cbret, r)) < 0)
if ((ret = rpc_callback_call(h, xbot, cbret, req)) < 0)
goto done;
if (ret > 0){ /* Handled locally */
if (clixon_xml_parse_string(cbuf_get(cbret), YB_NONE, NULL, &xret, NULL) < 0)
goto done;
/* Local error: return it and quit */
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -900,7 +942,7 @@ api_operations_post(clicon_handle h,
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
goto done;
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, media_out, 0) < 0)
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
@ -913,16 +955,14 @@ api_operations_post(clicon_handle h,
clicon_log_xml(LOG_DEBUG, xret, "%s Receive reply:", __FUNCTION__);
#endif
youtput = yang_find(yrpc, Y_OUTPUT, NULL);
if ((ret = api_operations_post_output(h, r, xret, yspec, youtput, namespace,
if ((ret = api_operations_post_output(h, req, xret, yspec, youtput, namespace,
pretty, media_out, &xoutput)) < 0)
goto done;
if (ret == 0)
goto ok;
/* xoutput should now look: <output xmlns="uri"><x>0</x></output> */
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
cbuf_reset(cbret);
switch (media_out){
case YANG_DATA_XML:
@ -938,8 +978,8 @@ api_operations_post(clicon_handle h,
default:
break;
}
FCGX_FPrintF(r->out, "%s", cbuf_get(cbret));
FCGX_FPrintF(r->out, "\r\n\r\n");
if (restconf_reply_send(req, 200, cbret) < 0)
goto done;
ok:
retval = 0;
done:

View file

@ -3,6 +3,7 @@
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
@ -34,22 +35,19 @@
* Restconf method implementation for post: operation(rpc) and data
*/
#ifndef _RESTCONF_METHODS_POST_H_
#define _RESTCONF_METHODS_POST_H_
/*
* Prototypes
*/
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
int pi,
cvec *qvec, char *data,
int api_data_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data,
int pretty,
restconf_media media_out);
int api_operations_post(clicon_handle h, FCGX_Request *r, char *api_path,
int api_operations_post(clicon_handle h, void *req, char *api_path,
int pi, cvec *qvec, char *data,
int pretty, restconf_media media_out);
#endif /* _RESTCONF_METHODS_POST_H_ */

View file

@ -63,13 +63,13 @@
/* restconf */
#include "restconf_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_root.h"
/*! Determine the root of the RESTCONF API
* @param[in] h Clicon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] cb Body buffer
* @see RFC8040 3.1 and RFC7320
* In line with the best practices defined by [RFC7320], RESTCONF
* enables deployments to specify where the RESTCONF API is located.
@ -92,9 +92,10 @@ api_well_known(clicon_handle h,
restconf_method_notallowed(req, "GET");
goto ok;
}
restconf_reply_status_code(req, 200); /* OK */
restconf_reply_header_add(req, "Cache-Control", "no-cache");
restconf_reply_header_add(req, "Content-Type", "application/xrd+xml");
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_header(req, "Content-Type", "application/xrd+xml") < 0)
goto done;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
@ -105,8 +106,9 @@ api_well_known(clicon_handle h,
cprintf(cb, "</XRD>\r\n");
/* Must be after body */
restconf_reply_header_add(req, "Content-Length", "%d", cbuf_len(cb));
if (restconf_reply_send(req, cb) < 0)
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done;
ok:
retval = 0;
@ -116,3 +118,461 @@ api_well_known(clicon_handle h,
return retval;
}
/*! Retrieve the Top-Level API Resource
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @note Only returns null for operations and data,...
* See RFC8040 3.3
* XXX doesnt check method
*/
static int
api_root(clicon_handle h,
void *req,
char *request_method,
int pretty,
restconf_media media_out)
{
int retval = -1;
yang_stmt *yspec;
cxobj *xt = NULL;
cbuf *cb = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (strcmp(request_method, "GET") != 0){
restconf_method_notallowed(req, "GET");
goto ok;
}
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (clixon_xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/>"
"<operations/><yang-library-version>2016-06-21</yang-library-version></restconf>",
YB_MODULE, yspec, &xt, NULL) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
switch (media_out){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done;
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done;
break;
default:
break;
}
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done;
ok:
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
}
/*!
* See https://tools.ietf.org/html/rfc7895
*/
static int
api_yang_library_version(clicon_handle h,
void *req,
int pretty,
restconf_media media_out)
{
#if 1
clicon_debug(1, "%s", __FUNCTION__);
return 0;
#else
int retval = -1;
cxobj *xt = NULL;
cbuf *cb = NULL;
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Content-Type: %s\r\n", restconf_media_int2str(media_out));
FCGX_FPrintF(r->out, "\r\n");
if (clixon_xml_parse_va(YB_NONE, NULL, &xt, NULL,
"<yang-library-version>%s</yang-library-version>",
ietf_yang_library_revision) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
if ((cb = cbuf_new()) == NULL){
goto done;
}
switch (media_out){
case YANG_DATA_XML:
if (clicon_xml2cbuf(cb, xt, 0, pretty, -1) < 0)
goto done;
break;
case YANG_DATA_JSON:
if (xml2json_cbuf(cb, xt, pretty) < 0)
goto done;
break;
default:
break;
}
clicon_debug(1, "%s cb%s", __FUNCTION__, cbuf_get(cb));
FCGX_FPrintF(r->out, "%s\n", cb?cbuf_get(cb):"");
FCGX_FPrintF(r->out, "\n\n");
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
#endif
}
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] pretty Set to 1 for pretty-printed xml/json output
* @param[in] media_in Input media
* @param[in] media_out Output media
*/
static int
api_data(clicon_handle h,
void *req,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
#if 1
clicon_debug(1, "%s", __FUNCTION__);
return 0;
#else
int retval = -1;
char *request_method;
clicon_debug(1, "%s", __FUNCTION__);
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, req);
else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, req, api_path, pcvec, pi, qvec, pretty, media_out);
else if (strcmp(request_method, "POST")==0)
retval = api_data_post(h, req, api_path, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "PATCH")==0)
retval = api_data_patch(h, req, api_path, pcvec, pi, qvec, data, pretty, media_out);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, req, api_path, pi, pretty, media_out);
else
retval = restconf_notfound(h, req);
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
#endif
}
/*! Move back to restconf_methods_get
*/
static int
api_operations_get(clicon_handle h,
void *req,
char *path,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
int retval = -1;
yang_stmt *yspec;
yang_stmt *ymod; /* yang module */
yang_stmt *yc;
char *namespace;
cbuf *cb = NULL;
cxobj *xt = NULL;
int i;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "<operations>");
break;
case YANG_DATA_JSON:
if (pretty)
cprintf(cb, "{\"operations\": {\n");
else
cprintf(cb, "{\"operations\":{");
break;
default:
break;
}
ymod = NULL;
i = 0;
while ((ymod = yn_each(yspec, ymod)) != NULL) {
namespace = yang_find_mynamespace(ymod);
yc = NULL;
while ((yc = yn_each(ymod, yc)) != NULL) {
if (yang_keyword_get(yc) != Y_RPC)
continue;
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "<%s xmlns=\"%s\"/>", yang_argument_get(yc), namespace);
break;
case YANG_DATA_JSON:
if (i++){
cprintf(cb, ",");
if (pretty)
cprintf(cb, "\n\t");
}
if (pretty)
cprintf(cb, "\"%s:%s\": [null]", yang_argument_get(ymod), yang_argument_get(yc));
else
cprintf(cb, "\"%s:%s\":[null]", yang_argument_get(ymod), yang_argument_get(yc));
break;
default:
break;
}
}
}
switch (media_out){
case YANG_DATA_XML:
cprintf(cb, "</operations>");
break;
case YANG_DATA_JSON:
if (pretty)
cprintf(cb, "}\n}");
else
cprintf(cb, "}}");
break;
default:
break;
}
if (restconf_reply_header(req, "Content-Type", "%s", restconf_media_int2str(media_out)) < 0)
goto done;
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (restconf_reply_send(req, 200, cb) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
}
/*! Operations REST method, POST
* @param[in] h CLIXON handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] request_method eg GET,...
* @param[in] path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* @param[in] media_out Output media
*/
static int
api_operations(clicon_handle h,
void *req,
char *request_method,
char *path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int pretty,
restconf_media media_out)
{
int retval;
clicon_debug(1, "%s", __FUNCTION__);
if (strcmp(request_method, "GET")==0)
retval = api_operations_get(h, req, path, pi, qvec, data, pretty, media_out);
#ifdef NYI
else if (strcmp(request_method, "POST")==0)
retval = api_operations_post(h, req, path, pi, qvec, data,
pretty, media_out);
#endif
else
retval = restconf_notfound(h, req);
return retval;
}
/*! Process a /restconf root input, this is the root of the restconf processing
* @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
*/
int
api_root_restconf(clicon_handle h,
void *req,
cvec *qvec)
{
int retval = -1;
char *request_method = NULL; /* GET,.. */
char *api_resource = NULL; /* RFC8040 3.3: eg data/operations */
char *path;
char **pvec = NULL;
cvec *pcvec = NULL; /* for rest api */
int pn;
int pretty;
cbuf *cb = NULL;
char *media_str = NULL;
restconf_media media_out = YANG_DATA_JSON;
char *indata = NULL;
int authenticated = 0;
cxobj *xret = NULL;
cxobj *xerr;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
errno = EINVAL;
goto done;
}
request_method = clixon_restconf_param_get(h, "REQUEST_METHOD");
path = restconf_uripath(h);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* Get media for output (proactive negotiation) RFC7231 by using
* Accept:. This is for methods that have output, such as GET,
* operation POST, etc
* If accept is * default is yang-json
*/
if ((media_str = clixon_restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
// retval = restconf_unsupported_media(r);
// goto done;
}
else if ((int)(media_out = restconf_media_str2int(media_str)) == -1){
if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON;
else{
retval = restconf_unsupported_media(req);
goto done;
}
}
clicon_debug(1, "%s ACCEPT: %s %s", __FUNCTION__, media_str, restconf_media_int2str(media_out));
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /restconf/ */
if (pn < 2){
restconf_notfound(h, req);
goto ok;
}
if (strlen(pvec[0]) != 0){
retval = restconf_notfound(h, req);
goto done;
}
if (strcmp(pvec[1], RESTCONF_API)){
retval = restconf_notfound(h, req);
goto done;
}
if (pn == 2){
retval = api_root(h, req, request_method, pretty, media_out);
goto done;
}
if ((api_resource = pvec[2]) == NULL){
retval = restconf_notfound(h, req);
goto done;
}
clicon_debug(1, "%s: api_resource=%s", __FUNCTION__, api_resource);
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = restconf_get_indata(req)) == NULL) /* XXX NYI ACTUALLY not always needed, do this later? */
goto done;
indata = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, indata);
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((authenticated = clixon_plugin_auth_all(h, req)) < 0)
goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, set a dummy user */
if (authenticated){
if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none");
}
else{
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
goto done;
if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){
if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
goto ok;
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (strcmp(api_resource, "yang-library-version")==0){
if (api_yang_library_version(h, req, pretty, media_out) < 0) /* XXX NYI */
goto done;
}
else if (strcmp(api_resource, "data") == 0){ /* restconf, skip /api/data */ /* XXX NYI */
if (api_data(h, req, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else if (strcmp(api_resource, "operations") == 0){ /* rpc */
if (api_operations(h, req, request_method, path, pcvec, 2, qvec, indata,
pretty, media_out) < 0)
goto done;
}
else
restconf_notfound(h, req);
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pcvec)
cvec_free(pcvec);
if (pvec)
free(pvec);
if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
return retval;
}

View file

@ -53,5 +53,6 @@
* Prototypes
*/
int api_well_known(clicon_handle h, void *req);
int api_root_restconf(clicon_handle h, void *req, cvec *qvec);
#endif /* _RESTCONF_ROOT_H_ */

View file

@ -86,7 +86,8 @@
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h"
#include "restconf_fcgi_lib.h"
#include "restconf_api.h"
#include "restconf_err.h"
#include "restconf_stream.h"
/*
@ -377,7 +378,6 @@ api_stream(clicon_handle h,
path = restconf_uripath(h);
query = clixon_restconf_param_get(h, "QUERY_STRING");
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
restconf_test(r, 1);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
@ -404,7 +404,7 @@ api_stream(clicon_handle h,
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(r)) == NULL)
if ((cb = restconf_get_indata(r)) == NULL)
goto done;
data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);