* 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:
parent
b680e3c5ac
commit
84f5762ab5
59 changed files with 1683 additions and 1107 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
221
apps/restconf/restconf_native.c
Normal file
221
apps/restconf/restconf_native.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue