* Most tests work with HTTP/2 support using nghttp2

* except non-ssl http/1->/2 upgrade
* Restconf: ensure HEAD method works everywhere GET does.
This commit is contained in:
Olof hagsand 2021-06-08 20:53:43 +02:00
parent b680e3c5ac
commit 84f5762ab5
59 changed files with 1683 additions and 1107 deletions

View file

@ -100,6 +100,7 @@ APPSRC += restconf_methods_get.c
APPSRC += restconf_root.c
APPSRC += restconf_main_$(with_restconf).c
ifeq ($(with_restconf),native)
APPSRC += restconf_native.c
APPSRC += restconf_evhtp.c # HTTP/1
APPSRC += restconf_nghttp2.c # HTTP/2
endif

View file

@ -44,10 +44,11 @@
#if defined(__GNUC__) && __GNUC__ >= 3
int restconf_reply_header(void *req, const char *name, const char *vfmt, ...) __attribute__ ((format (printf, 3, 4)));
#else
int restconf_reply_header(FCGX_Request *req, const char *name, const char *vfmt, ...);
int restconf_reply_header(void *req, const char *name, const char *vfmt, ...);
#endif
int restconf_reply_send(void *req, int code, cbuf *cb);
/* note cb is consumed dont free */
int restconf_reply_send(void *req, int code, cbuf *cb, int head);
cbuf *restconf_get_indata(void *req);

View file

@ -186,14 +186,15 @@ restconf_reply_body_add(void *req0,
/*! Send HTTP reply with potential message body
* @param[in] req Fastcgi request handle
* @param[in] code Status code
* @param[in] cb Body as a cbuf if non-NULL
* @param[in] cb Body as a cbuf if non-NULL. Note is consumed
*
* Prerequisites: status code set, headers given, body if wanted set
*/
int
restconf_reply_send(void *req0,
int code,
cbuf *cb)
cbuf *cb,
int head)
{
FCGX_Request *req = (FCGX_Request *)req0;
int retval = -1;
@ -206,9 +207,12 @@ restconf_reply_send(void *req0,
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, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "\r\n");
if (cb != NULL){
if (!head && cbuf_len(cb)){
FCGX_FPrintF(req->out, "%s", cbuf_get(cb));
FCGX_FPrintF(req->out, "\r\n");
}
cbuf_free(cb);
}
FCGX_FFlush(req->out); /* Is this only for notification ? */
retval = 0;

View file

@ -60,7 +60,6 @@
#include <nghttp2/nghttp2.h>
#endif
/* cligen */
#include <cligen/cligen.h>
@ -83,19 +82,23 @@ restconf_reply_header(void *req0,
const char *vfmt,
...)
{
#ifdef HAVE_LIBEVHTP
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
size_t vlen;
char *value = NULL;
va_list ap;
evhtp_connection_t *conn;
restconf_conn_h *rc;
int retval = -1;
restconf_stream_data *sd = (restconf_stream_data *)req0;
restconf_conn *rc;
size_t vlen;
char *value = NULL;
va_list ap;
if (req == NULL || name == NULL || vfmt == NULL){
clicon_err(OE_CFG, EINVAL, "req, name or value is NULL");
return -1;
clicon_debug(1, "%s %s", __FUNCTION__, name);
if (sd == NULL || name == NULL || vfmt == NULL){
clicon_err(OE_CFG, EINVAL, "sd, name or value is NULL");
goto done;
}
if ((rc = sd->sd_conn) == NULL){
clicon_err(OE_CFG, EINVAL, "rc is NULL");
goto done;
}
/* First round: compute vlen and allocate value */
va_start(ap, vfmt);
vlen = vsnprintf(NULL, 0, vfmt, ap);
va_end(ap);
@ -104,7 +107,7 @@ restconf_reply_header(void *req0,
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: compute actual value */
/* Second round: compute actual value */
va_start(ap, vfmt);
if (vsnprintf(value, vlen+1, vfmt, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
@ -112,15 +115,7 @@ restconf_reply_header(void *req0,
goto done;
}
va_end(ap);
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
if ((rc = conn->arg) == NULL){
clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen");
goto done;
}
if (cvec_add_string(rc->rc_outp_hdrs, (char*)name, value) < 0){
if (cvec_add_string(sd->sd_outp_hdrs, (char*)name, value) < 0){
clicon_err(OE_RESTCONF, errno, "cvec_add_string");
goto done;
}
@ -129,130 +124,60 @@ restconf_reply_header(void *req0,
if (value)
free(value);
return retval;
#else /* HAVE_LIBEVHTP */
return 0;
#endif /* HAVE_LIBEVHTP */
}
#ifdef HAVE_LIBEVHTP
/*! Send reply
* @see htp__create_reply_
*/
#define rc_parser conn->parser /* XXX */
static int
native_send_reply(restconf_conn_h *rc,
evhtp_request_t *request,
evhtp_res code)
{
int retval = -1;
unsigned char major;
unsigned char minor;
cg_var *cv;
switch (request->proto) {
case EVHTP_PROTO_10:
if (request->flags & EVHTP_REQ_FLAG_KEEPALIVE) {
/* protocol is HTTP/1.0 and clients wants to keep established */
if (restconf_reply_header(request, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(request->rc_parser); /* XXX Look origin */
minor = htparser_get_minor(request->rc_parser);
break;
case EVHTP_PROTO_11:
if (!(request->flags & EVHTP_REQ_FLAG_KEEPALIVE)) {
/* protocol is HTTP/1.1 but client wanted to close */
if (restconf_reply_header(request, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(request->rc_parser);
minor = htparser_get_minor(request->rc_parser);
break;
default:
/* this sometimes happens when a response is made but paused before
* the method has been parsed */
major = 1;
minor = 0;
break;
}
cprintf(rc->rc_outp_buf, "HTTP/%u.%u %u %s\r\n", major, minor, code, restconf_code2reason(code));
/* Loop over headers */
cv = NULL;
while ((cv = cvec_each(rc->rc_outp_hdrs, cv)) != NULL)
cprintf(rc->rc_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv));
cprintf(rc->rc_outp_buf, "\r\n");
// cvec_reset(rc->rc_outp_hdrs); /* Is now done in restconf_connection but can be done here */
retval = 0;
done:
return retval;
}
#endif /* HAVE_LIBEVHTP */
/*! Send HTTP reply with potential message body
* @param[in] req Evhtp http request handle
* @param[in] cb Body as a cbuf, send if
* @param[in] req http request handle
* @param[in] cb Body as a cbuf if non-NULL. Note is consumed
* @param[in] head Only send headers, dont send body.
*
* Prerequisites: status code set, headers given, body if wanted set
*/
int
restconf_reply_send(void *req0,
int code,
cbuf *cb)
cbuf *cb,
int head)
{
#ifdef HAVE_LIBEVHTP
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
const char *reason_phrase;
evhtp_connection_t *conn;
restconf_conn_h *rc;
int retval = -1;
restconf_stream_data *sd = (restconf_stream_data *)req0;
clicon_debug(1, "%s code:%d", __FUNCTION__, code);
req->status = code;
if ((reason_phrase = restconf_code2reason(code)) == NULL)
reason_phrase="";
#if 0 /* XXX remove status header for evhtp? */
if (restconf_reply_header(req, "Status", "%d %s", code, reason_phrase) < 0)
if (sd == NULL){
clicon_err(OE_CFG, EINVAL, "sd is NULL");
goto done;
}
sd->sd_code = code;
if (cb != NULL){
if (cbuf_len(cb)){
cprintf(cb, "\r\n");
#if 0 /* Double for evhtp, single for nghttp2 */
if (restconf_reply_header(sd, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
#endif
if ((conn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
goto done;
}
/* If body, add a content-length header
* A server MUST NOT send a Content-Length header field in any response
* with a status code of 1xx (Informational) or 204 (No Content). A
* server MUST NOT send a Content-Length header field in any 2xx
* (Successful) response to a CONNECT request (Section 4.3.6 of
* [RFC7231]).
*/
if (cb != NULL && cbuf_len(cb)){
cprintf(cb, "\r\n");
if (restconf_reply_header(req, "Content-Length", "%d", cbuf_len(cb)) < 0)
goto done;
if (head)
cbuf_free(cb);
else{
sd->sd_body = cb;
sd->sd_body_offset = 0;
}
}
else{
cbuf_free(cb);
#if 0
if (restconf_reply_header(sd, "Content-Length", "0") < 0)
goto done;
#endif
}
}
#if 0
else
if (restconf_reply_header(req, "Content-Length", "0") < 0)
goto done;
if ((rc = conn->arg) == NULL){
clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen");
goto done;
}
/* Create reply and write headers */
if (native_send_reply(rc, req, code) < 0)
goto done;
req->flags |= EVHTP_REQ_FLAG_FINISHED; /* Signal to evhtp to read next request */
/* Write a body if cbuf is nonzero */
if (cb != NULL && cbuf_len(cb)){
cprintf(rc->rc_outp_buf, "%s", cbuf_get(cb));
}
if (restconf_reply_header(sd, "Content-Length", "0") < 0)
goto done;
#endif
retval = 0;
done:
return retval;
#else /* HAVE_LIBEVHTP */
return 0;
#endif /* HAVE_LIBEVHTP */
}
/*! get input data
@ -262,27 +187,15 @@ restconf_reply_send(void *req0,
cbuf *
restconf_get_indata(void *req0)
{
cbuf *cb = NULL;
#ifdef HAVE_LIBEVHTP
evhtp_request_t *req = (evhtp_request_t *)req0;
size_t len;
unsigned char *buf;
if ((cb = cbuf_new()) == NULL)
return NULL;
len = evbuffer_get_length(req->buffer_in);
if (len > 0){
if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){
clicon_err(OE_CFG, errno, "evbuffer_pullup");
return NULL;
}
/* Note the pullup may not be null-terminated */
cbuf_append_buf(cb, buf, len);
restconf_stream_data *sd = (restconf_stream_data *)req0;
cbuf *cb = NULL;
if (sd == NULL){
clicon_err(OE_CFG, EINVAL, "sd is NULL");
goto done;
}
cb = sd->sd_indata;
done:
return cb;
#else /* HAVE_LIBEVHTP */
return cb;
#endif /* HAVE_LIBEVHTP */
}

View file

@ -147,7 +147,7 @@ restconf_not_acceptable(clicon_handle h,
/* Override with 415 netconf->restoconf translation which gives a 405 */
if (api_return_err0(h, req, xerr, pretty, media, 415) < 0)
goto done;
if (restconf_reply_send(req, 415, NULL) < 0)
if (restconf_reply_send(req, 415, NULL, 0) < 0)
goto done;
retval = 0;
done:
@ -301,8 +301,9 @@ api_return_err(clicon_handle h,
break;
} /* switch media */
assert(cbuf_len(cb));
if (restconf_reply_send(req, code, cb) < 0)
if (restconf_reply_send(req, code, cb, 0) < 0)
goto done;
cb = NULL;
// ok:
retval = 0;
done:

View file

@ -54,6 +54,8 @@
#include <arpa/inet.h>
#include <sys/resource.h>
#include <openssl/ssl.h>
/* cligen */
#include <cligen/cligen.h>
@ -61,13 +63,12 @@
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h> /* XXX inline this / use SSL directly */
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2 /* To get restconf_native.h include files right */
@ -81,7 +82,9 @@
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBEVHTP
#include "restconf_evhtp.h" /* evhtp http/1 */
#endif
#ifdef HAVE_LIBEVHTP
static char*
@ -264,15 +267,19 @@ evhtp_params_set(clicon_handle h,
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
/* Slightly awkward way of taking cert subject and CN and add it to restconf parameters
* instead of accessing it directly */
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (ssl_x509_name_oneline(req->conn->ssl, &subject) < 0)
goto done;
if (subject != NULL) {
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
if (restconf_param_set(h, "SSL_CN", cn) < 0) /* Can be used by callback */
goto done;
}
}
@ -298,6 +305,111 @@ evhtp_params_set(clicon_handle h,
goto done;
}
/*! We got -1 back from lower layers, create a 500 Internal Server error
* Catch all on fatal error. This does not terminate the process but closes request
* stream
*/
static int
evhtp_internal_error(evhtp_request_t *req)
{
if (strlen(clicon_err_reason) &&
req->buffer_out){
evbuffer_add_printf(req->buffer_out, "%s", clicon_err_reason);
evhtp_send_reply(req, EVHTP_RES_500);
}
clicon_err_reset();
return 0;
}
/*! Send reply
* @see htp__create_reply_
*/
static int
native_send_reply(restconf_conn *rc,
restconf_stream_data *sd,
evhtp_request_t *req)
{
int retval = -1;
cg_var *cv;
int minor;
int major;
switch (req->proto) {
case EVHTP_PROTO_10:
if (req->flags & EVHTP_REQ_FLAG_KEEPALIVE) {
/* protocol is HTTP/1.0 and clients wants to keep established */
if (restconf_reply_header(sd, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(req->conn->parser); /* XXX Look origin */
minor = htparser_get_minor(req->conn->parser);
break;
case EVHTP_PROTO_11:
if (!(req->flags & EVHTP_REQ_FLAG_KEEPALIVE)) {
/* protocol is HTTP/1.1 but client wanted to close */
if (restconf_reply_header(sd, "Connection", "keep-alive") < 0)
goto done;
}
major = htparser_get_major(req->conn->parser);
minor = htparser_get_minor(req->conn->parser);
break;
default:
/* this sometimes happens when a response is made but paused before
* the method has been parsed */
major = 1;
minor = 0;
break;
}
cprintf(sd->sd_outp_buf, "HTTP/%u.%u %u %s\r\n",
major,
minor,
sd->sd_code,
restconf_code2reason(sd->sd_code));
/* Loop over headers */
cv = NULL;
while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL)
cprintf(sd->sd_outp_buf, "%s: %s\r\n", cv_name_get(cv), cv_string_get(cv));
cprintf(sd->sd_outp_buf, "\r\n");
// cvec_reset(rc->rc_outp_hdrs); /* Is now done in restconf_connection but can be done here */
retval = 0;
done:
return retval;
}
/*!
*/
static int
restconf_evhtp_reply(restconf_conn *rc,
restconf_stream_data *sd,
evhtp_request_t *req)
{
int retval = -1;
req->status = sd->sd_code;
req->flags |= EVHTP_REQ_FLAG_FINISHED; /* Signal to evhtp to read next request */
/* If body, add a content-length header
* A server MUST NOT send a Content-Length header field in any response
* with a status code of 1xx (Informational) or 204 (No Content). A
* server MUST NOT send a Content-Length header field in any 2xx
* (Successful) response to a CONNECT request (Section 4.3.6 of
* [RFC7231]).
*/
if (restconf_reply_header(sd, "Content-Length", "%d",
(sd->sd_body!=NULL)?cbuf_len(sd->sd_body):0) < 0)
goto done;
/* Create reply and write headers */
if (native_send_reply(rc, sd, req) < 0)
goto done;
/* Write a body */
if (sd->sd_body){
cbuf_append_str(sd->sd_outp_buf, cbuf_get(sd->sd_body));
}
retval = 0;
done:
return retval;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
@ -314,16 +426,35 @@ void
restconf_path_root(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
evhtp_connection_t *evconn;
restconf_conn *rc;
restconf_stream_data *sd;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
evhtp_internal_error(req);
goto done;
}
/* evhtp connect struct */
if ((evconn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
evhtp_internal_error(req);
goto done;
}
/* get clixon request connect pointer from generic evhtp application pointer */
rc = evconn->arg;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
evhtp_internal_error(req);
goto done;
}
sd->sd_req = req;
sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11;
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
@ -332,27 +463,55 @@ restconf_path_root(evhtp_request_t *req,
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
evhtp_internal_error(req);
goto done;
}
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, qvec)) < 0)
/* Get indata
*/
{
size_t len;
unsigned char *buf;
len = evbuffer_get_length(req->buffer_in);
if (len > 0){
if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){
clicon_err(OE_CFG, errno, "evbuffer_pullup");
goto done;
}
/* Note the pullup may not be null-terminated */
cbuf_append_buf(sd->sd_indata, buf, len);
}
}
/* set fcgi-like paramaters (ignore query vector)
* ret = 0 means an error has already been sent
*/
if ((ret = evhtp_params_set(h, req, qvec)) < 0){
evhtp_internal_error(req);
goto done;
}
if (ret == 1){
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
if (api_root_restconf(h, sd, qvec) < 0){
evhtp_internal_error(req);
goto done;
}
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
/* Clear input request parameters from this request */
if (restconf_param_del_all(h) < 0){
evhtp_internal_error(req);
goto done;
}
/* All parameters for sending a reply are here
*/
if (sd->sd_code){
if (restconf_evhtp_reply(rc, sd, req) < 0){
evhtp_internal_error(req);
goto done;
}
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
if (qvec)
cvec_free(qvec);
return; /* void */
@ -368,37 +527,67 @@ void
restconf_path_wellknown(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
int retval = -1;
clicon_handle h;
int ret;
evhtp_connection_t *evconn;
restconf_conn *rc;
restconf_stream_data *sd;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
evhtp_internal_error(req);
goto done;
}
/* evhtp connect struct */
if ((evconn = evhtp_request_get_connection(req)) == NULL){
clicon_err(OE_DAEMON, EFAULT, "evhtp_request_get_connection");
evhtp_internal_error(req);
goto done;
}
/* get clixon request connect pointer from generic evhtp application pointer */
rc = evconn->arg;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No stream_data");
evhtp_internal_error(req);
goto done;
}
sd->sd_req = req;
sd->sd_proto = (req->proto == EVHTP_PROTO_10)?HTTP_10:HTTP_11;
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, NULL)) < 0)
if ((ret = evhtp_params_set(h, req, NULL)) < 0){
evhtp_internal_error(req);
goto done;
}
if (ret == 1){
/* call generic function */
if (api_well_known(h, req) < 0)
if (api_well_known(h, sd) < 0){
evhtp_internal_error(req);
goto done;
}
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
/* Clear input request parameters from this request */
if (restconf_param_del_all(h) < 0){
evhtp_internal_error(req);
goto done;
}
/* All parameters for sending a reply are here
*/
if (sd->sd_code){
if (restconf_evhtp_reply(rc, sd, req) < 0){
evhtp_internal_error(req);
goto done;
}
}
retval = 0;
done:
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return; /* void */
}
#endif /* HAVE_LIBEVHTP */

View file

@ -530,24 +530,30 @@ restconf_main_extension_cb(clicon_handle h,
return retval;
}
/*! Extract uri-encoded uri-path from fastcgi parameters
/*! Extract uri-encoded uri-path without arguments
*
* Use REQUEST_URI parameter and strip ?args
* REQUEST_URI have args and is encoded
* eg /interface=eth%2f0%2f0?insert=first
* DOCUMENT_URI dont have args and is not encoded
* eg /interface=eth/0/0
* causes problems with eg /interface=eth%2f0%2f0
* eg /interface=eth%2f0%2f0?insert=first -> /interface=eth%2f0%2f0
* @retval path malloced, need free
*/
char *
restconf_uripath(clicon_handle h)
{
char *path;
char *path = NULL;
char *path2 = NULL;
char *q;
path = restconf_param_get(h, "REQUEST_URI");
if ((q = index(path, '?')) != NULL)
if ((path = restconf_param_get(h, "REQUEST_URI")) == NULL){
clicon_err(OE_RESTCONF, 0, "No REQUEST_URI");
return NULL;
}
if ((path2 = strdup(path)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
return NULL;
}
if ((q = index(path2, '?')) != NULL)
*q = '\0';
return path;
return path2;
}
/*! Drop privileges from root to user (or already at user)
@ -936,3 +942,4 @@ restconf_socket_extract(clicon_handle h,
free(reason);
return retval;
}

View file

@ -158,7 +158,7 @@
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h> /* XXX inline this / use SSL directly */
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
@ -266,6 +266,7 @@ buf_write(char *buf,
memcpy(dbgstr, buf, sz);
dbgstr[sz] = '\0';
clicon_debug(1, "%s buflen:%lu buf:%s", __FUNCTION__, buflen, dbgstr);
free(dbgstr);
}
while (totlen < buflen){
if (ssl){
@ -392,7 +393,6 @@ restconf_verify_certs(int preverify_ok,
// SSL *ssl;
// clicon_handle h;
clicon_debug(1, "%s %d", __FUNCTION__, preverify_ok);
err_cert = X509_STORE_CTX_get_current_cert(store);
err = X509_STORE_CTX_get_error(store);
depth = X509_STORE_CTX_get_error_depth(store);
@ -406,14 +406,13 @@ restconf_verify_certs(int preverify_ok,
break;
}
/* Catch a too long certificate chain. should be +1 in SSL_CTX_set_verify_depth() */
if (depth > 1) {
if (depth > VERIFY_DEPTH + 1) {
preverify_ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(store, err);
}
if (depth == VERIFY_DEPTH){
else{
/* Verify the CA name */
}
// h = SSL_get_app_data(ssl);
return preverify_ok;
@ -422,28 +421,25 @@ restconf_verify_certs(int preverify_ok,
/*! Debug print of all incoming alpn alternatives, eg h2 and http/1.1
*/
static int
dump_alpn_proto_list(const unsigned char *in,
unsigned int inlen)
alpn_proto_dump(const char *label,
const char *inp,
int len)
{
unsigned char *inp;
unsigned char len;
char *str;
inp = (unsigned char*)in;
while ((inp-in) < inlen) {
len = *inp;
inp++;
if ((str = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return -1;
}
strncpy(str, (const char*)inp, len);
str[len] = '\0';
clicon_debug(1, "%s %s", __FUNCTION__, str);
free(str);
inp += len;
int retval = -1;
char *str = NULL;
if ((str = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
return 0;
strncpy(str, inp, len);
str[len] = '\0';
clicon_debug(1, "%s %s", label, str);
retval = 0;
done:
free(str);
return retval;
}
/*! Application-layer Protocol Negotiation (alpn) callback
@ -464,13 +460,13 @@ alpn_select_proto_cb(SSL *ssl,
int pref = 0;
clicon_debug(1, "%s", __FUNCTION__);
if (clicon_debug_get())
dump_alpn_proto_list(in, inlen);
/* select http/1.1 */
inp = (unsigned char*)in;
while ((inp-in) < inlen) {
len = *inp;
inp++;
if (clicon_debug_get()) /* debug print the protoocol */
alpn_proto_dump(__FUNCTION__, (const char*)inp, len);
if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){
*outlen = len;
*out = inp;
@ -488,6 +484,7 @@ alpn_select_proto_cb(SSL *ssl,
}
if (pref == 0)
return SSL_TLSEXT_ERR_NOACK;
alpn_proto_dump("ALPN selected:", (const char*)*out, *outlen);
return SSL_TLSEXT_ERR_OK;
}
@ -584,29 +581,13 @@ restconf_ssl_context_configure(clixon_handle h,
return retval;
}
/*! Free clixon/cbuf resources related to an evhtp connection
* @param[in] rc restconf connection
*/
static int
restconf_conn_free(restconf_conn_h *rc)
{
if (rc != NULL){
if (rc->rc_outp_hdrs)
cvec_free(rc->rc_outp_hdrs);
if (rc->rc_outp_buf)
cbuf_free(rc->rc_outp_buf);
free(rc);
}
return 0;
}
/*! Utility function to close restconf server ssl/evhtp socket.
* There are many variants to closing, one could probably make this more generic
* and always use this function, but it is difficult.
*/
static int
close_ssl_socket(restconf_conn_h *rc,
int shutdown)
restconf_close_ssl_socket(restconf_conn *rc,
int shutdown)
{
int retval = -1;
int ret;
@ -619,6 +600,10 @@ close_ssl_socket(restconf_conn_h *rc,
#endif /* HAVE_LIBEVHTP */
if (rc->rc_ssl != NULL){
if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){
#if 0
case SSL_ERROR_ZERO_RETURN: /* 6 */
Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that the underlying transport has been closed.
#endif
int e = SSL_get_error(rc->rc_ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
@ -631,7 +616,6 @@ close_ssl_socket(restconf_conn_h *rc,
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
restconf_conn_free(rc);
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
@ -696,18 +680,19 @@ static int
restconf_connection(int s,
void *arg)
{
int retval = -1;
restconf_conn_h *rc = NULL;
ssize_t n;
char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */
int readmore = 1;
int retval = -1;
restconf_conn *rc = NULL;
ssize_t n;
char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */
int readmore = 1;
#ifdef HAVE_LIBEVHTP
clicon_handle h;
evhtp_connection_t *evconn = NULL;
clicon_handle h;
evhtp_connection_t *evconn = NULL;
restconf_stream_data *sd;
#endif
clicon_debug(1, "%s %d", __FUNCTION__, s);
if ((rc = (restconf_conn_h*)arg) == NULL){
if ((rc = (restconf_conn*)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
@ -741,93 +726,107 @@ restconf_connection(int s,
clicon_debug(1, "%s read:%ld", __FUNCTION__, n);
if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (close_ssl_socket(rc, 1) < 0)
if (restconf_close_ssl_socket(rc, 1) < 0)
goto done;
restconf_conn_free(rc);
rc = NULL;
goto ok;
}
switch (rc->rc_proto){
#ifdef HAVE_LIBEVHTP
case HTTP_10:
case HTTP_11:
h = rc->rc_h;
/* parse incoming packet using evhtp
* signature:
*/
evconn = rc->rc_evconn;
if (connection_parse_nobev(buf, n, evconn) < 0){
clicon_debug(1, "%s connection_parse error", __FUNCTION__);
/* XXX To get more nuanced evhtp error check
* htparser_get_error(conn->parser)
h = rc->rc_h;
/* parse incoming packet using evhtp
* signature:
*/
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message></error></errors>") < 0)
goto done;
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
evconn->ssl = NULL;
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
evconn = rc->rc_evconn;
/* This is the main call to EVHTP parser */
if (connection_parse_nobev(buf, n, evconn) < 0){
clicon_debug(1, "%s connection_parse error", __FUNCTION__);
/* XXX To get more nuanced evhtp error check
* htparser_get_error(conn->parser)
*/
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>The requested URL or a header is in some way badly formed</error-message></error></errors>") < 0)
goto done;
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
evconn->ssl = NULL;
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn);
restconf_conn_free(rc);
evhtp_connection_free(evconn);
goto ok;
}
clicon_debug(1, "%s connection_parse OK", __FUNCTION__);
/* default stream */
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn);
restconf_conn_free(rc);
evhtp_connection_free(evconn);
goto ok;
}
clicon_debug(1, "%s connection_parse OK", __FUNCTION__);
if (evconn->bev != NULL){
struct evbuffer *ev;
size_t buflen0;
size_t buflen1;
char *buf = NULL;
if (evconn->bev != NULL){
struct evbuffer *ev;
size_t buflen0;
size_t buflen1;
char *buf = NULL;
if ((ev = bufferevent_get_output(evconn->bev)) != NULL){
buflen0 = evbuffer_get_length(ev);
buflen1 = buflen0 - rc->rc_bufferevent_output_offset;
if (buflen1 > 0){
buf = (char*)evbuffer_pullup(ev, -1);
/* If evhtp has print an output buffer, clixon whould not have done it
* Shouldnt happen
*/
if (cbuf_len(rc->rc_outp_buf)){
clicon_debug(1, "%s Warning: evhtp printed output buffer, but clixon output buffer is non-empty %s",
__FUNCTION__, cbuf_get(rc->rc_outp_buf));
cbuf_reset(rc->rc_outp_buf);
if ((ev = bufferevent_get_output(evconn->bev)) != NULL){
buflen0 = evbuffer_get_length(ev);
buflen1 = buflen0 - rc->rc_bufferevent_output_offset;
if (buflen1 > 0){
buf = (char*)evbuffer_pullup(ev, -1);
/* XXX Here if -1 in api_root
* HTTP/1.1 0 UNKNOWN\r\nContent-Length: 0
* And output_buffer is NULL
*/
/* If evhtp has print an output buffer, clixon whould not have done it
* Shouldnt happen
*/
if (cbuf_len(sd->sd_outp_buf)){
clicon_debug(1, "%s Warning: evhtp printed output buffer, but clixon output buffer is non-empty %s",
__FUNCTION__, cbuf_get(sd->sd_outp_buf));
cbuf_reset(sd->sd_outp_buf);
}
if (cbuf_append_buf(sd->sd_outp_buf, buf, buflen1) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append_buf");
goto done;
}
/* XXX Cant get drain to work, need to keep an offset */
evbuffer_drain(ev, -1);
rc->rc_bufferevent_output_offset += buflen1;
}
if (cbuf_append_buf(rc->rc_outp_buf, buf, buflen1) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append_buf");
}
if (cbuf_len(sd->sd_outp_buf) == 0)
readmore = 1;
else {
if (buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
rc->rc_s, rc->rc_ssl) < 0)
goto done;
}
/* XXX Cant get drain to work, need to keep an offset */
evbuffer_drain(ev, -1);
rc->rc_bufferevent_output_offset += buflen1;
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(sd->sd_outp_buf);
}
}
if (cbuf_len(rc->rc_outp_buf) == 0)
readmore = 1;
else {
if (buf_write(cbuf_get(rc->rc_outp_buf), cbuf_len(rc->rc_outp_buf),
rc->rc_s, rc->rc_ssl) < 0)
else{
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>No evhtp output</error-message></error></errors>") < 0)
goto done;
cvec_reset(rc->rc_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(rc->rc_outp_buf);
}
}
else{
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>No evhtp output</error-message></error></errors>") < 0)
goto done;
}
break;
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:
if (http2_recv(rc, (unsigned char *)buf, n) < 0)
goto done;
// notused sd = restconf_stream_find(rc, 0); /* default stream */
break;
#endif /* HAVE_LIBNGHTTP2 */
default:
break;
default:
break;
} /* switch rc_proto */
} /* while readmore */
ok:
@ -913,7 +912,7 @@ static int
ssl_alpn_check(clicon_handle h,
const unsigned char *alpn,
unsigned int alpnlen,
restconf_conn_h *rc,
restconf_conn *rc,
restconf_http_proto *proto)
{
int retval = -1;
@ -998,7 +997,7 @@ restconf_accept_client(int fd,
int retval = -1;
restconf_socket *rsock;
restconf_native_handle *rh = NULL;
restconf_conn_h *rc = NULL;
restconf_conn *rc = NULL;
clicon_handle h;
int s;
struct sockaddr from = {0,};
@ -1008,12 +1007,15 @@ restconf_accept_client(int fd,
int e;
int er;
int readmore;
X509 *peercert;
const unsigned char *alpn = NULL;
unsigned int alpnlen = 0;
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
restconf_http_proto proto = HTTP_11; /* Non-SSL negotiation NYI */
clicon_debug(1, "%s %d", __FUNCTION__, fd);
#ifdef HAVE_LIBNGHTTP2
/* If nghttp2 let default be 2.0 NOTE http protocol negotiation */
proto = HTTP_2;
#endif
if ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
@ -1035,22 +1037,9 @@ restconf_accept_client(int fd,
/*
* Register callbacks for actual data socket
*/
if ((rc = (restconf_conn_h*)malloc(sizeof(restconf_conn_h))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
if ((rc = restconf_conn_new(h, s)) == NULL)
goto done;
}
memset(rc, 0, sizeof(restconf_conn_h));
rc->rc_h = h;
rc->rc_s = s;
clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s);
if ((rc->rc_outp_hdrs = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
if ((rc->rc_outp_buf = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (rsock->rs_ssl){
if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){
clicon_err(OE_SSL, 0, "SSL_new");
@ -1120,8 +1109,10 @@ restconf_accept_client(int fd,
operations should be performed on the connection and SSL_shutdown() must
not be called.*/
clicon_debug(1, "%s SSL_accept() SSL_ERROR_SYSCALL %d", __FUNCTION__, er);
if (close_ssl_socket(rc, 0) < 0)
if (restconf_close_ssl_socket(rc, 0) < 0)
goto done;
restconf_conn_free(rc);
rc = NULL;
goto ok;
break;
case SSL_ERROR_WANT_READ: /* 2 */
@ -1152,17 +1143,33 @@ restconf_accept_client(int fd,
}
} /* SSL_accept */
} /* while(readmore) */
/* Sets data and len to point to the client's requested protocol for this connection. */
SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen);
if (alpn == NULL) {
/* Returns a pointer to the selected protocol in data with length len. */
SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen);
}
if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0)
goto done;
if (ret == 0)
goto ok;
clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto));
#if 0 /* Seems too early to fail here, instead let authentication callback deal with this */
/* For client-cert authentication, check if any certs are present,
* if not, send bad request
* Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
* but then SSL_accept fails.
*/
if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){
X509 *peercert;
if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){
X509_free(peercert);
}
else { /* Get certificates (if available) */
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
if (proto != HTTP_2 &&
send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>") < 0)
goto done;
restconf_conn_free(rc);
@ -1178,17 +1185,7 @@ restconf_accept_client(int fd,
goto ok;
}
}
/* Sets data and len to point to the client's requested protocol for this connection. */
SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen);
if (alpn == NULL) {
/* Returns a pointer to the selected protocol in data with length len. */
SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen);
}
if ((ret = ssl_alpn_check(h, alpn, alpnlen, rc, &proto)) < 0)
goto done;
if (ret == 0)
goto ok;
clicon_debug(1, "%s proto:%s", __FUNCTION__, restconf_proto2str(proto));
#endif
/* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ?
* Note this _only_ works if SSL_set1_host() was set previously,...
*/
@ -1196,7 +1193,7 @@ restconf_accept_client(int fd,
const char *peername = SSL_get0_peername(rc->rc_ssl);
if (peername != NULL) {
if (peername != NULL) {
/* Name checks were in scope and matched the peername */
clicon_debug(1, "%s peername:%s", __FUNCTION__, peername);
}
@ -1224,13 +1221,36 @@ restconf_accept_client(int fd,
rc->rc_evconn = evconn; /* Generic to specific */
evconn->arg = rc; /* Specific to generic */
evconn->ssl = rc->rc_ssl; /* evhtp */
/* Create a default stream for http/1 */
if (restconf_stream_data_new(rc, 0) == NULL)
goto done;
}
break;
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:{
if (http2_session_init(rc) < 0)
if (http2_session_init(rc) < 0){
restconf_close_ssl_socket(rc, 1);
goto done;
}
if (http2_send_server_connection(rc) < 0){
restconf_close_ssl_socket(rc, 1);
#ifdef NYI
if (ssl) {
SSL_shutdown(ssl);
}
bufferevent_free(session_data->bev);
nghttp2_session_del(session_data->session);
for (stream_data = session_data->root.next; stream_data;) {
http2_stream_data *next = stream_data->next;
delete_http2_stream_data(stream_data);
stream_data = next;
}
free(session_data->client_addr);
free(session_data);
#endif
goto done;
}
break;
}
#endif /* HAVE_LIBNGHTTP2 */

View file

@ -99,7 +99,7 @@ api_data_options(clicon_handle h,
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)
if (restconf_reply_send(req, 200, NULL, 0) < 0)
goto done;
retval = 0;
done:
@ -547,11 +547,11 @@ api_data_write(clicon_handle h,
if ((xe = xpath_first(xret, NULL, "//ok")) != NULL &&
(attr = xml_find_value(xe, "objectexisted")) != NULL &&
strcmp(attr, "false")==0){
if (restconf_reply_send(req, 201, NULL) < 0) /* Created */
if (restconf_reply_send(req, 201, NULL, 0) < 0) /* Created */
goto done;
}
else
if (restconf_reply_send(req, 204, NULL) < 0) /* No content */
if (restconf_reply_send(req, 204, NULL, 0) < 0) /* No content */
goto done;
ok:
retval = 0;
@ -784,7 +784,7 @@ api_data_delete(clicon_handle h,
goto done;
goto ok;
}
if (restconf_reply_send(req, 204, NULL) < 0)
if (restconf_reply_send(req, 204, NULL, 0) < 0)
goto done;
ok:
retval = 0;

View file

@ -85,7 +85,7 @@
* Response contains one of:
* Content-Type: application/yang-data+xml
* Content-Type: application/yang-data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* @note: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
@ -217,16 +217,6 @@ api_data_get2(clicon_handle h,
/* Normal return, no error */
if ((cbx = cbuf_new()) == NULL)
goto done;
if (head){
/* 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 */
switch (media_out){
case YANG_DATA_XML:
@ -295,8 +285,9 @@ api_data_get2(clicon_handle h,
goto done;
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (restconf_reply_send(req, 200, cbx) < 0)
if (restconf_reply_send(req, 200, cbx, head) < 0)
goto done;
cbx = NULL;
ok:
retval = 0;
done:
@ -489,13 +480,13 @@ api_operations_get(clicon_handle h,
default:
break;
}
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)
if (restconf_reply_send(req, 200, cbx, 0) < 0)
goto done;
cbx = NULL;
// ok:
retval = 0;
done:

View file

@ -84,7 +84,8 @@ http_location_header(clicon_handle h,
https = restconf_param_get(h, "HTTPS");
host = restconf_param_get(h, "HTTP_HOST");
request_uri = restconf_param_get(h, "REQUEST_URI");
if ((request_uri = restconf_uripath(h)) == NULL)
goto done;
if (xobj != NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "cbuf_new");
@ -109,6 +110,8 @@ http_location_header(clicon_handle h,
done:
if (cb)
cbuf_free(cb);
if (request_uri)
free(request_uri);
return retval;
}
@ -367,7 +370,7 @@ api_data_post(clicon_handle h,
}
if (http_location_header(h, req, xdata) < 0)
goto done;
if (restconf_reply_send(req, 201, NULL) < 0)
if (restconf_reply_send(req, 201, NULL, 0) < 0)
goto done;
ok:
retval = 0;
@ -621,7 +624,7 @@ api_operations_post_output(clicon_handle h,
strcmp(xml_name(xok),"ok")==0);
if (isempty) {
/* Internal error - invalid output from rpc handler */
if (restconf_reply_send(req, 204, NULL) < 0)
if (restconf_reply_send(req, 204, NULL, 0) < 0)
goto done;
goto fail;
}
@ -872,8 +875,9 @@ api_operations_post(clicon_handle h,
default:
break;
}
if (restconf_reply_send(req, 200, cbret) < 0)
goto done;
if (restconf_reply_send(req, 200, cbret, 0) < 0)
goto done;
cbret = NULL;
ok:
retval = 0;
done:

View file

@ -0,0 +1,221 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <assert.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/resource.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#ifdef HAVE_LIBEVHTP
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#endif
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
restconf_stream_data *
restconf_stream_data_new(restconf_conn *rc,
int32_t stream_id)
{
restconf_stream_data *sd;
if ((sd = malloc(sizeof(restconf_stream_data))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return NULL;
}
memset(sd, 0, sizeof(restconf_stream_data));
sd->sd_stream_id = stream_id;
sd->sd_fd = -1;
if ((sd->sd_indata = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
return NULL;
}
if ((sd->sd_outp_hdrs = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
return NULL;
}
if ((sd->sd_outp_buf = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
return NULL;
}
sd->sd_conn = rc;
INSQ(sd, rc->rc_streams);
return sd;
}
restconf_stream_data *
restconf_stream_find(restconf_conn *rc,
int32_t id)
{
restconf_stream_data *sd;
if ((sd = rc->rc_streams) != NULL) {
do {
if (sd->sd_stream_id == id)
return sd;
sd = NEXTQ(restconf_stream_data *, sd);
} while (sd && sd != rc->rc_streams);
}
return NULL;
}
int
restconf_stream_free(restconf_stream_data *sd)
{
if (sd->sd_fd != -1) {
close(sd->sd_fd);
}
if (sd->sd_indata)
cbuf_free(sd->sd_indata);
if (sd->sd_outp_hdrs)
cvec_free(sd->sd_outp_hdrs);
if (sd->sd_outp_buf)
cbuf_free(sd->sd_outp_buf);
if (sd->sd_body)
cbuf_free(sd->sd_body);
if (sd->sd_path)
free(sd->sd_path);
free(sd);
return 0;
}
/*! Create restconf connection struct
*/
restconf_conn *
restconf_conn_new(clicon_handle h,
int s)
{
restconf_conn *rc;
if ((rc = (restconf_conn*)malloc(sizeof(restconf_conn))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return NULL;
}
memset(rc, 0, sizeof(restconf_conn));
rc->rc_h = h;
rc->rc_s = s;
return rc;
}
/*! Free clixon/cbuf resources related to an evhtp connection
* @param[in] rc restconf connection
*/
int
restconf_conn_free(restconf_conn *rc)
{
restconf_stream_data *sd;
if (rc == NULL){
clicon_err(OE_RESTCONF, EINVAL, "rc is NULL");
return -1;
}
/* Free all streams */
while ((sd = rc->rc_streams) != NULL) {
DELQ(sd, rc->rc_streams, restconf_stream_data *);
if (sd)
restconf_stream_free(sd);
}
free(rc);
return 0;
}
/*! Given SSL connection, get peer certificate one-line name
* @param[in] ssl SSL session
* @param[out] oneline Cert name one-line
*/
int
ssl_x509_name_oneline(SSL *ssl,
char **oneline)
{
int retval = -1;
char *p = NULL;
X509 *cert = NULL;
X509_NAME *name;
if (ssl == NULL || oneline == NULL) {
clicon_err(OE_RESTCONF, EINVAL, "ssl or cn is NULL");
goto done;
}
if ((cert = SSL_get_peer_certificate(ssl)) == NULL)
goto ok;
if ((name = X509_get_subject_name(cert)) == NULL)
goto ok;
if ((p = X509_NAME_oneline(name, NULL, 0)) == NULL)
goto ok;
if ((*oneline = strdup(p)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
ok:
retval = 0;
done:
if (p)
OPENSSL_free(p);
if (cert)
X509_free(cert);
return retval;
}

View file

@ -46,7 +46,6 @@
* | rr restconf_request| per-packet
* +--------------------+
*
* Parse functions
*/
#ifdef __cplusplus
@ -59,40 +58,50 @@ extern "C" {
/*
* Types
*/
/* http/2 session stream struct
/* Forward */
struct restconf_conn;
/* session stream struct, mainly for http/2 but htp/1 has a single pseudo-stream with id=0
*/
typedef struct {
qelem_t sd_qelem; /* List header */
int32_t sd_stream_id;
int sd_fd;
qelem_t sd_qelem; /* List header */
int32_t sd_stream_id;
int sd_fd; /* XXX Is this used? */
cvec *sd_outp_hdrs; /* List of output headers */
cbuf *sd_outp_buf; /* Output buffer */
cbuf *sd_body; /* http output body as cbuf terminated with \r\n */
size_t sd_body_offset; /* Offset into body */
cbuf *sd_indata; /* Receive/input data */
char *sd_path; /* Uri path, uri-encoded, without args (eg ?) */
uint16_t sd_code; /* If != 0 send a reply XXX: need reply flag? */
struct restconf_conn *sd_conn; /* Backpointer to connection this stream is part of */
restconf_http_proto sd_proto; /* http protocol XXX not sure this is needed */
void *sd_req; /* Lib-specific request, eg evhtp_request_t * */
} restconf_stream_data;
/* Restconf connection handle
/* Restconf connection handle
* Per connection request
*/
typedef struct {
typedef struct restconf_conn {
// qelem_t rs_qelem; /* List header */
cvec *rc_outp_hdrs; /* List of output headers */
cbuf *rc_outp_buf; /* Output buffer */
size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
int rc_s; /* Connection socket */
clicon_handle rc_h; /* Clixon handle */
SSL *rc_ssl; /* Structure for SSL connection */
restconf_stream_data *rc_streams; /* List of http/2 session streams */
restconf_stream_data *rc_streams; /* List of http/2 session streams */
/* Decision to keep lib-specific data here, otherwise new struct necessary
* drawback is specific includes need to go everywhere */
#ifdef HAVE_LIBEVHTP
evhtp_connection_t *rc_evconn;
#endif
#ifdef HAVE_LIBNGHTTP2
nghttp2_session *rc_ngsession;
nghttp2_session *rc_ngsession; /* XXX Not sure it is needed */
#endif
} restconf_conn_h;
/* Restconf request handle
* Per socket request
} restconf_conn;
/* Restconf per socket handle
*/
typedef struct {
qelem_t rs_qelem; /* List header */
@ -117,8 +126,13 @@ typedef struct {
/*
* Prototypes
*/
int restconf_parse(void *req, const char *buf, size_t buflen);
restconf_stream_data *restconf_stream_data_new(restconf_conn *rc, int32_t stream_id);
restconf_stream_data *restconf_stream_find(restconf_conn *rc, int32_t id);
int restconf_stream_free(restconf_stream_data *sd);
restconf_conn *restconf_conn_new(clicon_handle h, int s);
int restconf_conn_free(restconf_conn *rc);
int ssl_x509_name_oneline(SSL *ssl, char **oneline);
#endif /* _RESTCONF_NATIVE_H_ */
#ifdef __cplusplus

View file

@ -32,6 +32,18 @@
***** END LICENSE BLOCK *****
* nghttp2 callback mechanism
*
* nghttp2_session_mem_recv()
* on_begin_headers_callback()
* create sd
* on_header_callback() NGHTTP2_HEADERS
* translate all headers
* on_data_chunk_recv_callback
* get indata
* on_frame_recv_callback NGHTTP2_FLAG_END_STREAM
* get method and call handler
* create rr
*/
#ifdef HAVE_CONFIG_H
@ -93,53 +105,23 @@
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
static restconf_stream_data *
restconf_stream_data_new(restconf_conn_h *rc,
int32_t stream_id)
{
restconf_stream_data *sd;
sd = malloc(sizeof(restconf_stream_data));
memset(sd, 0, sizeof(restconf_stream_data));
sd->sd_stream_id = stream_id;
sd->sd_fd = -1;
INSQ(sd, rc->rc_streams);
return sd;
}
#ifdef NOTUSED
static void
delete_http2_stream_data(restconf_stream_data *sd)
{
if (sd->fd != -1) {
close(sd->fd);
}
free(sd->request_path);
free(sd);
}
#endif
#ifdef NOTUSED
static int
send_client_connection_header(nghttp2_session *session)
{
int retval = -1;
nghttp2_settings_entry iv[1] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
int rv;
clicon_debug(1, "%s", __FUNCTION__);
/* client 24 bytes magic string will be sent by nghttp2 library */
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv));
if (rv != 0) {
clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
goto done;
}
retval = 0;
done:
return retval;
}
#endif /* NOTUSED */
/*! Map http2 frame types in nghttp2
* I had expected it in in libnghttp2 but havent found it
*/
static const map_str2int nghttp2_frame_type_map[] = {
{"DATA", NGHTTP2_DATA},
{"HEADERS", NGHTTP2_HEADERS},
{"PRIORITY", NGHTTP2_PRIORITY},
{"RST_STREAM", NGHTTP2_RST_STREAM},
{"SETTINGS", NGHTTP2_SETTINGS},
{"PUSH_PROMISE", NGHTTP2_PUSH_PROMISE},
{"PING", NGHTTP2_PING},
{"GOAWAY", NGHTTP2_GOAWAY},
{"WINDOW_UPDATE", NGHTTP2_WINDOW_UPDATE},
{"CONTINUATION", NGHTTP2_CONTINUATION},
{"ALTSVC", NGHTTP2_ALTSVC},
{NULL, -1}
};
/* Clixon error category specialized log callback for nghttp2
* @param[in] handle Application-specific handle
@ -156,7 +138,6 @@ clixon_nghttp2_log_cb(void *handle,
return 0;
}
#ifdef NOTUSED
static void
nghttp2_print_header(const uint8_t *name,
@ -181,35 +162,94 @@ nghttp2_print_headers(nghttp2_nv *nva,
}
#endif /* NOTUSED */
/*! Transmit the |data|, |length| bytes, to the network.
/*! Send data to remote peer, Send at most the |length| bytes of |data|.
* This callback is required if the application uses
* `nghttp2_session_send()` to send data to the remote endpoint. If
* the application uses solely `nghttp2_session_mem_send()` instead,
* this callback function is unnecessary.
* XXX see buf_write
*/
static ssize_t
send_callback(nghttp2_session *session,
const uint8_t *data,
size_t length,
int flags,
void *user_data)
session_send_callback(nghttp2_session *session,
const uint8_t *buf,
size_t buflen,
int flags,
void *user_data)
{
restconf_conn_h *rc = (restconf_conn_h *)user_data;
int ret;
int retval = -1;
restconf_conn *rc = (restconf_conn *)user_data;
int er;
ssize_t len;
ssize_t totlen = 0;
int s;
SSL *ssl;
clicon_debug(1, "%s %zu:", __FUNCTION__, length);
#if 0
{
int i;
for (i=0; i<length; i++)
fprintf(stderr, "%02x", data[i]&255);
fprintf(stderr, "\n");
}
clicon_debug(1, "%s buflen:%lu", __FUNCTION__, buflen);
s = rc->rc_s;
ssl = rc->rc_ssl;
while (totlen < buflen){
if (ssl){
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
er = errno;
switch (SSL_get_error(ssl, len)){
case SSL_ERROR_SYSCALL: /* 5 */
if (er == ECONNRESET) {/* Connection reset by peer */
if (ssl)
SSL_free(ssl);
close(s);
// XXX clixon_event_unreg_fd(s, restconf_connection);
goto ok; /* Close socket and ssl */
}
else if (er == EAGAIN){
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
usleep(10000);
continue;
}
else{
clicon_err(OE_RESTCONF, er, "SSL_write %d", er);
goto done;
}
break;
default:
clicon_err(OE_SSL, 0, "SSL_write");
goto done;
break;
}
goto done;
}
}
else{
if ((len = write(s, buf+totlen, buflen-totlen)) < 0){
if (errno == EAGAIN){
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
usleep(10000);
continue;
}
#if 1
else if (errno == ECONNRESET) {/* Connection reset by peer */
close(s);
// XXX clixon_event_unreg_fd(s, restconf_connection);
goto ok; /* Close socket and ssl */
}
#endif
/* encrypt & send message */
if ((ret = SSL_write(rc->rc_ssl, data, length)) < 0)
return ret;
return ret;
else{
clicon_err(OE_UNIX, errno, "write");
goto done;
}
}
assert(len != 0);
}
totlen += len;
} /* while */
ok:
retval = 0;
done:
if (retval < 0){
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
clicon_debug(1, "%s retval:%lu", __FUNCTION__, totlen);
return totlen;
}
/*! Invoked when |session| wants to receive data from the remote peer.
@ -221,7 +261,7 @@ recv_callback(nghttp2_session *session,
int flags,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
@ -239,31 +279,57 @@ recv_callback(nghttp2_session *session,
* 2) terminating the process?
*/
static int
restconf_nghttp2_root(restconf_conn_h *rc)
restconf_nghttp2_path(restconf_stream_data *sd)
{
int retval = -1;
clicon_handle h;
// int ret;
cvec *qvec = NULL;
int retval = -1;
clicon_handle h;
cvec *qvec = NULL;
char *query = NULL;
restconf_conn *rc;
char *oneline = NULL;
cvec *cvv = NULL;
char *cn;
clicon_debug(1, "------------");
rc = sd->sd_conn;
if ((h = rc->rc_h) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
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;
query = restconf_param_get(h, "REQUEST_URI");
if ((query = index(query, '?')) != NULL){
query++;
if (strlen(query) &&
uri_str2cvec(query, '&', '=', 1, &qvec) < 0)
goto done;
}
/* Slightly awkward way of taking SSL cert subject and CN and add it to restconf parameters
* instead of accessing it directly */
if (rc->rc_ssl != NULL){
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if (ssl_x509_name_oneline(rc->rc_ssl, &oneline) < 0)
goto done;
if (oneline != NULL) {
if (uri_str2cvec(oneline, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
goto done;
}
}
}
/* call generic function */
if (api_root_restconf(h, rc, qvec) < 0)
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
if (api_well_known(h, sd) < 0)
goto done;
}
else if (api_root_restconf(h, sd, qvec) < 0)
goto done;
// /* Clear (fcgi) paramaters from this request */
// if (restconf_param_del_all(h) < 0)
// goto done;
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
@ -271,12 +337,123 @@ restconf_nghttp2_root(restconf_conn_h *rc)
// if (retval < 0){
// evhtp_send_reply(req, EVHTP_RES_ERROR);
// }
if (cvv)
cvec_free(cvv);
if (oneline)
free(oneline);
if (qvec)
cvec_free(qvec);
return retval; /* void */
}
/*!
/*! data callback, just pass pointer to cbuf
* XXX handle several chunks with cbuf
*/
static ssize_t
restconf_sd_read(nghttp2_session *session,
int32_t stream_id,
uint8_t *buf,
size_t length,
uint32_t *data_flags,
nghttp2_data_source *source,
void *user_data)
{
restconf_stream_data *sd = (restconf_stream_data *)source->ptr;
cbuf *cb;
size_t len = 0;
size_t remain;
if ((cb = sd->sd_body) == NULL){ /* shouldnt happen */
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0;
}
#if 0
if (cbuf_len(cb) <= length){
len = remain;
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
else{
len = length;
}
memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len);
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return len;
#endif
assert(cbuf_len(cb) > sd->sd_body_offset);
remain = cbuf_len(cb) - sd->sd_body_offset;
clicon_debug(1, "%s length:%lu totlen:%d, offset:%lu remain:%lu",
__FUNCTION__,
length,
cbuf_len(cb),
sd->sd_body_offset,
remain);
if (remain <= length){
len = remain;
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
else{
len = length;
}
memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len);
sd->sd_body_offset += len;
clicon_debug(1, "%s retval:%lu", __FUNCTION__, len);
return len;
}
static int
restconf_submit_response(nghttp2_session *session,
restconf_conn *rc,
int stream_id,
restconf_stream_data *sd)
{
int retval = -1;
nghttp2_data_provider data_prd;
nghttp2_error ngerr;
cg_var *cv;
nghttp2_nv *hdrs;
nghttp2_nv *hdr;
int i = 0;
char valstr[16];
clicon_debug(1, "%s", __FUNCTION__);
data_prd.source.ptr = sd;
data_prd.read_callback = restconf_sd_read;
if ((hdrs = (nghttp2_nv*)calloc(1+cvec_len(sd->sd_outp_hdrs), sizeof(nghttp2_nv))) == NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
hdr = &hdrs[i++];
hdr->name = (uint8_t*)":status";
snprintf(valstr, 15, "%u", sd->sd_code);
hdr->value = (uint8_t*)valstr;
hdr->namelen = strlen(":status");
hdr->valuelen = strlen(valstr);
clicon_debug(1, "%s val:'%s' valuelen:%lu", __FUNCTION__, hdr->value, hdr->valuelen);
hdr->flags = 0;
cv = NULL;
while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL){
hdr = &hdrs[i++];
hdr->name = (uint8_t*)cv_name_get(cv);
hdr->value = (uint8_t*)cv_string_get(cv);
hdr->namelen = strlen(cv_name_get(cv));
hdr->valuelen = strlen(cv_string_get(cv));
hdr->flags = 0;
}
if ((ngerr = nghttp2_submit_response(session,
stream_id,
hdrs, i,
(data_prd.source.ptr != NULL)?&data_prd:NULL)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response");
goto done;
}
retval = 0;
done:
return retval;
}
/*! A frame is received
*/
static int
on_frame_recv_callback(nghttp2_session *session,
@ -284,30 +461,39 @@ on_frame_recv_callback(nghttp2_session *session,
void *user_data)
{
int retval = -1;
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_stream_data *sd;
char *path;
restconf_conn *rc = (restconf_conn *)user_data;
restconf_stream_data *sd = NULL;
clicon_debug(1, "%s %d", __FUNCTION__, frame->hd.stream_id);
clicon_debug(1, "%s %s %d", __FUNCTION__,
clicon_int2str(nghttp2_frame_type_map, frame->hd.type),
frame->hd.stream_id);
switch (frame->hd.type) {
case NGHTTP2_DATA:
case NGHTTP2_HEADERS:
/* Check that the client request has finished */
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
/* For DATA and HEADERS frame, this callback may be called after
on_stream_close_callback. Check that stream still alive. */
on_stream_close_callback. Check that stream still alive.
*/
if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL)
return 0;
if ((path = restconf_uripath(rc->rc_h)) == NULL)
if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL)
goto ok;
if (strcmp(path, "/" RESTCONF_API) == 0){
if (restconf_nghttp2_root(rc) < 0)
sd->sd_proto = HTTP_2; /* XXX is this necessary? */
if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 ||
strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){
if (restconf_nghttp2_path(sd) < 0)
goto done;
}
else if (strcmp(path, RESTCONF_WELL_KNOWN) == 0){
}
else
; /* ignore */
if (sd->sd_code){
if (restconf_submit_response(session, rc, frame->hd.stream_id, sd) < 0)
goto done;
}
else {
/* 500 Internal server error ? */
}
}
break;
default:
@ -319,7 +505,7 @@ on_frame_recv_callback(nghttp2_session *session,
return retval;
}
/*!
/*! An invalid non-DATA frame is received.
*/
static int
on_invalid_frame_recv_callback(nghttp2_session *session,
@ -327,12 +513,12 @@ on_invalid_frame_recv_callback(nghttp2_session *session,
int lib_error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! A chunk of data in DATA frame is received
*/
static int
on_data_chunk_recv_callback(nghttp2_session *session,
@ -342,41 +528,41 @@ on_data_chunk_recv_callback(nghttp2_session *session,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_conn *rc = (restconf_conn *)user_data;
restconf_stream_data *sd;
clicon_debug(1, "%s %d", __FUNCTION__, stream_id);
// if (sd->sd_session == session &&
// sd->sd_stream_id == stream_id)
// fwrite(data, 1, len, stdout); /* This is where data is printed */
if ((sd = restconf_stream_find(rc, stream_id)) != NULL){
cbuf_append_buf(sd->sd_indata, (void*)data, len);
}
return 0;
}
/*!
/*! Just before the non-DATA frame |frame| is sent
*/
static int
before_frame_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! After the frame |frame| is sent
*/
static int
on_frame_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! After the non-DATA frame |frame| is not sent because of error
*/
static int
on_frame_not_send_callback(nghttp2_session *session,
@ -384,12 +570,12 @@ on_frame_not_send_callback(nghttp2_session *session,
int lib_error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! Stream |stream_id| is closed.
*/
static int
on_stream_close_callback(nghttp2_session *session,
@ -397,7 +583,7 @@ on_stream_close_callback(nghttp2_session *session,
nghttp2_error_code error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
//session_data *sd = (session_data*)user_data;
return 0;
@ -410,10 +596,10 @@ on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_conn *rc = (restconf_conn *)user_data;
restconf_stream_data *sd;
clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type));
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
sd = restconf_stream_data_new(rc, frame->hd.stream_id);
@ -422,45 +608,6 @@ on_begin_headers_callback(nghttp2_session *session,
return 0;
}
#ifdef XXX
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
static int
evhtp_convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
cbuf *cb = NULL;
int i;
char c;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* convert key name */
cprintf(cb, "HTTP_");
for (i=0; i<strlen(hdr->key); i++){
c = hdr->key[i] & 0xff;
if (islower(c))
cprintf(cb, "%c", toupper(c));
else if (c == '-')
cprintf(cb, "_");
else
cprintf(cb, "%c", c);
}
if (restconf_param_set(h, cbuf_get(cb), hdr->val) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
#endif
/*! Map from nghttp2 headers to "fcgi" type parameters used in clixon code
* Both |name| and |value| are guaranteed to be NULL-terminated.
*/
@ -472,8 +619,8 @@ nghttp2_hdr2clixon(clicon_handle h,
int retval = -1;
if (strcmp(name, ":path") == 0){
/* XXX "/restconf" Is PATH really REQUEST_URI? */
if (restconf_param_set(h, "REQUEST_URI", value) < 0) /* XXX string? */
/* Including ?args, call restconf_uripath() to get only path */
if (restconf_param_set(h, "REQUEST_URI", value) < 0)
goto done;
}
else if (strcmp(name, ":method") == 0){
@ -513,20 +660,17 @@ on_header_callback(nghttp2_session *session,
void *user_data)
{
int retval = -1;
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_stream_data *sd;
restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s %d:", __FUNCTION__, frame->hd.stream_id);
switch (frame->hd.type){
case NGHTTP2_HEADERS:
assert (frame->headers.cat == NGHTTP2_HCAT_REQUEST);
clicon_debug(1, "%s %s %s", __FUNCTION__, name, value);
if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL)
break;
clicon_debug(1, "%s HEADERS %s %s", __FUNCTION__, name, value);
if (nghttp2_hdr2clixon(rc->rc_h, (char*)name, (char*)value) < 0)
goto done;
break;
default:
clicon_debug(1, "%s %s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type), name);
break;
}
retval = 0;
@ -534,7 +678,8 @@ on_header_callback(nghttp2_session *session,
return retval;
}
/*!
#ifdef NOTUSED
/*! How many padding bytes are required for the transmission of the |frame|?
*/
static ssize_t
select_padding_callback(nghttp2_session *session,
@ -542,12 +687,12 @@ select_padding_callback(nghttp2_session *session,
size_t max_payloadlen,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
return frame->hd.length;
}
/*!
/*! Get max length of data to send data to the remote peer
*/
static ssize_t
data_source_read_length_callback(nghttp2_session *session,
@ -558,10 +703,11 @@ data_source_read_length_callback(nghttp2_session *session,
uint32_t remote_max_frame_size,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
#endif /* NOTUSED */
/*! Invoked when a frame header is received.
* Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will
@ -572,15 +718,17 @@ on_begin_frame_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s type:%d", __FUNCTION__, hd->type);
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, hd->type));
if (hd->type == NGHTTP2_CONTINUATION)
assert(0);
return 0;
}
/*!
/*! Send complete DATA frame for no-copy
* Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is
* used in :type:`nghttp2_data_source_read_callback` to send complete
* DATA frame.
*/
static int
send_data_callback(nghttp2_session *session,
@ -589,12 +737,13 @@ send_data_callback(nghttp2_session *session,
nghttp2_data_source *source,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
#ifdef NOTUSED
/*! Pack extension payload in its wire format
*/
static ssize_t
pack_extension_callback(nghttp2_session *session,
@ -602,12 +751,12 @@ pack_extension_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! Unpack extension payload from its wire format.
*/
static int
unpack_extension_callback(nghttp2_session *session,
@ -615,12 +764,13 @@ unpack_extension_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
#endif /* NOTUSED */
/*!
/*! Chunk of extension frame payload is received
*/
static int
on_extension_chunk_recv_callback(nghttp2_session *session,
@ -629,25 +779,12 @@ on_extension_chunk_recv_callback(nghttp2_session *session,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
error_callback(nghttp2_session *session,
const char *msg,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
/*! Library provides the error code, and message for debugging purpose.
*/
static int
error_callback2(nghttp2_session *session,
@ -656,8 +793,9 @@ error_callback2(nghttp2_session *session,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
// restconf_conn *rc = (restconf_conn *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
clicon_err(OE_NGHTTP2, lib_error_code, "%s", msg);
return 0;
}
@ -665,22 +803,57 @@ error_callback2(nghttp2_session *session,
* XXX see session_recv
*/
int
http2_recv(restconf_conn_h *rc,
http2_recv(restconf_conn *rc,
const unsigned char *buf,
size_t n)
{
int retval = -1;
nghttp2_error ngerr;
int retval = -1;
nghttp2_error ngerr;
clicon_debug(1, "%s", __FUNCTION__);
if (rc->rc_ngsession == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session");
/* http2_session_init not called */
clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session");
goto done;
}
/* may make additional pending frames */
if ((ngerr = nghttp2_session_mem_recv(rc->rc_ngsession, buf, n)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_mem_recv");
goto done;
}
/* sends highest prio frame from outbound queue to remote peer. It does this as
* many as possible until user callback :type:`nghttp2_send_callback` returns
* * :enum:`NGHTTP2_ERR_WOULDBLOCK` or the outbound queue becomes empty.
*/
if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send");
goto done;
}
retval = 0;
done:
return retval;
}
/* Send HTTP/2 client connection header, which includes 24 bytes
magic octets and SETTINGS frame */
int
http2_send_server_connection(restconf_conn *rc)
{
int retval = -1;
nghttp2_settings_entry iv[1] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
nghttp2_error ngerr;
if ((ngerr = nghttp2_submit_settings(rc->rc_ngsession,
NGHTTP2_FLAG_NONE,
iv,
ARRLEN(iv))) != 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_settings");
goto done;
}
if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send");
goto done;
}
retval = 0;
done:
return retval;
@ -689,13 +862,15 @@ http2_recv(restconf_conn_h *rc,
/*! Initialize callbacks
*/
int
http2_session_init(restconf_conn_h *rc)
http2_session_init(restconf_conn *rc)
{
int retval = -1;
nghttp2_session_callbacks *callbacks = NULL;
nghttp2_session *session = NULL;
nghttp2_error ngerr;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_send_callback(callbacks, session_send_callback);
nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(callbacks, on_invalid_frame_recv_callback);
@ -706,22 +881,31 @@ http2_session_init(restconf_conn_h *rc)
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback);
#ifdef NOTUSED
nghttp2_session_callbacks_set_select_padding_callback(callbacks, select_padding_callback);
nghttp2_session_callbacks_set_data_source_read_length_callback(callbacks, data_source_read_length_callback);
#endif
nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, on_begin_frame_callback);
nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback);
#ifdef NOTUSED
nghttp2_session_callbacks_set_pack_extension_callback(callbacks, pack_extension_callback);
nghttp2_session_callbacks_set_unpack_extension_callback(callbacks, unpack_extension_callback);
#endif
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(callbacks, on_extension_chunk_recv_callback);
nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2);
/* Register callbacks with nghttp2 */
nghttp2_session_server_new(&session, callbacks, rc);
/* Create session for server use, register callbacks */
if ((ngerr = nghttp2_session_server_new(&session, callbacks, rc)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_server_new");
goto done;
}
nghttp2_session_callbacks_del(callbacks);
rc->rc_ngsession = session;
return 0;
retval = 0;
done:
return retval;
}
#endif /* HAVE_LIBNGHTTP2 */

View file

@ -42,7 +42,8 @@
* Prototypes
*/
int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb);
int http2_recv(restconf_conn_h *rc, const unsigned char *buf, size_t n);
int http2_session_init(restconf_conn_h *rc);
int http2_recv(restconf_conn *rc, const unsigned char *buf, size_t n);
int http2_send_server_connection(restconf_conn *rc);
int http2_session_init(restconf_conn *rc);
#endif /* _RESTCONF_NGHTTP2_H_ */

View file

@ -85,6 +85,7 @@ api_well_known(clicon_handle h,
char *request_method;
cbuf *cb = NULL;
int pretty;
int head;
clicon_debug(1, "%s", __FUNCTION__);
if (req == NULL){
@ -92,16 +93,17 @@ api_well_known(clicon_handle h,
goto done;
}
request_method = restconf_param_get(h, "REQUEST_METHOD");
if (strcmp(request_method, "GET") != 0){
head = strcmp(request_method, "HEAD") == 0;
if (!head && strcmp(request_method, "GET") != 0){
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
if (restconf_method_notallowed(h, req, "GET", pretty, YANG_DATA_JSON) < 0)
if (restconf_method_notallowed(h, req, "GET,HEAD", pretty, YANG_DATA_JSON) < 0)
goto done;
goto ok;
}
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;
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
/* Create body */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
@ -111,8 +113,9 @@ api_well_known(clicon_handle h,
cprintf(cb, " <Link rel='restconf' href='/restconf'/>\n");
cprintf(cb, "</XRD>\r\n");
if (restconf_reply_send(req, 200, cb) < 0)
if (restconf_reply_send(req, 200, cb, head) < 0)
goto done;
cb = NULL;
ok:
retval = 0;
done:
@ -144,9 +147,11 @@ api_root_restconf_exact(clicon_handle h,
yang_stmt *yspec;
cxobj *xt = NULL;
cbuf *cb = NULL;
int head;
clicon_debug(1, "%s", __FUNCTION__);
if (strcmp(request_method, "GET") != 0){
head = strcmp(request_method, "HEAD") == 0;
if (!head && strcmp(request_method, "GET") != 0){
if (restconf_method_notallowed(h, req, "GET", pretty, media_out) < 0)
goto done;
goto ok;
@ -155,11 +160,10 @@ api_root_restconf_exact(clicon_handle h,
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 (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done;
if (clixon_xml_parse_string("<restconf xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><data/>"
"<operations/><yang-library-version>" IETF_YANG_LIBRARY_REVISION
"</yang-library-version></restconf>",
@ -184,8 +188,9 @@ api_root_restconf_exact(clicon_handle h,
default:
break;
}
if (restconf_reply_send(req, 200, cb) < 0)
if (restconf_reply_send(req, 200, cb, head) < 0)
goto done;
cb = NULL;
ok:
retval = 0;
done:
@ -261,8 +266,9 @@ api_yang_library_version(clicon_handle h,
default:
break;
}
if (restconf_reply_send(req, 200, cb) < 0)
if (restconf_reply_send(req, 200, cb, 0) < 0)
goto done;
cb = NULL;
retval = 0;
done:
if (cb)
@ -414,7 +420,7 @@ api_root_restconf(clicon_handle h,
int retval = -1;
char *request_method = NULL; /* GET,.. */
char *api_resource = NULL; /* RFC8040 3.3: eg data/operations */
char *path;
char *path = NULL;
char **pvec = NULL;
cvec *pcvec = NULL; /* for rest api */
int pn;
@ -433,7 +439,8 @@ api_root_restconf(clicon_handle h,
goto done;
}
request_method = restconf_param_get(h, "REQUEST_METHOD");
path = restconf_uripath(h);
if ((path = restconf_uripath(h)) == NULL)
goto done;
/* XXX see restconf_config_init access directly */
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* Get media for output (proactive negotiation) RFC7231 by using
@ -462,6 +469,7 @@ api_root_restconf(clicon_handle h,
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /restconf/ */
if (pn < 2){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /restconf/ expected") < 0)
@ -576,7 +584,6 @@ api_root_restconf(clicon_handle h,
goto done;
goto ok;
}
ok:
retval = 0;
done:
@ -589,8 +596,8 @@ api_root_restconf(clicon_handle h,
cvec_free(pcvec);
if (pvec)
free(pvec);
if (cb)
cbuf_free(cb);
if (path)
free(path);
return retval;
}

View file

@ -304,7 +304,7 @@ restconf_stream(clicon_handle h,
goto done;
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
goto done;
if (restconf_reply_send(req, 201, NULL) < 0)
if (restconf_reply_send(req, 201, NULL, 0) < 0)
goto done;
*sp = s;
ok:
@ -378,7 +378,7 @@ api_stream(clicon_handle h,
{
int retval = -1;
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
char *path;
char *path = NULL;
char *method;
char **pvec = NULL;
int pn;
@ -397,7 +397,8 @@ api_stream(clicon_handle h,
#endif
clicon_debug(1, "%s", __FUNCTION__);
path = restconf_uripath(h);
if ((path = restconf_uripath(h)) == NULL)
goto done;
/* XXX see restconf_config_init access directly */
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
@ -521,5 +522,7 @@ api_stream(clicon_handle h,
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (path)
free(path);
return retval;
}