- Started nghttp2 work

-  Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2`.
- Added special case for api-path:s beginning with //
This commit is contained in:
Olof hagsand 2021-05-31 19:03:19 +02:00
parent c405a08ff8
commit 0ad577fa81
14 changed files with 634 additions and 291 deletions

View file

@ -34,6 +34,8 @@ Expected: June 2021
### New features
* Started EXPERIMENTAL HTTP/2 work using nghttp2
* Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2` enabling http/1 only / http/2 only linking
* YANG when statement in conjunction with grouping/uses/augment
* Several cases were not implemented fully according to RFC 7950:
* Do not extend default values if when statements evaluate to false

View file

@ -49,11 +49,13 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#endif /* HAVE_LIBEVHTP */
/* cligen */
#include <cligen/cligen.h>
@ -77,6 +79,7 @@ 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;
@ -122,8 +125,12 @@ 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_
*/
@ -177,6 +184,7 @@ native_send_reply(restconf_conn_h *rc,
done:
return retval;
}
#endif /* HAVE_LIBEVHTP */
/*! Send HTTP reply with potential message body
* @param[in] req Evhtp http request handle
@ -189,6 +197,7 @@ restconf_reply_send(void *req0,
int code,
cbuf *cb)
{
#ifdef HAVE_LIBEVHTP
evhtp_request_t *req = (evhtp_request_t *)req0;
int retval = -1;
const char *reason_phrase;
@ -237,6 +246,9 @@ restconf_reply_send(void *req0,
retval = 0;
done:
return retval;
#else /* HAVE_LIBEVHTP */
return 0;
#endif /* HAVE_LIBEVHTP */
}
/*! get input data
@ -246,8 +258,10 @@ restconf_reply_send(void *req0,
cbuf *
restconf_get_indata(void *req0)
{
evhtp_request_t *req = (evhtp_request_t *)req0;
cbuf *cb = NULL;
#ifdef HAVE_LIBEVHTP
evhtp_request_t *req = (evhtp_request_t *)req0;
size_t len;
unsigned char *buf;
@ -262,7 +276,9 @@ restconf_get_indata(void *req0)
/* Note the pullup may not be null-terminated */
cbuf_append_buf(cb, buf, len);
}
return cb;
#else /* HAVE_LIBEVHTP */
return cb;
#endif /* HAVE_LIBEVHTP */
}

View file

@ -210,6 +210,14 @@ static const map_str2int http_media_map[] = {
{NULL, -1}
};
/* Mapping to http proto types */
static const map_str2int http_proto_map[] = {
{"http/1.0", HTTP_10},
{"http/1.1", HTTP_11},
{"http/2", HTTP_2},
{NULL, -1}
};
int
restconf_err2code(char *tag)
{
@ -234,6 +242,18 @@ restconf_media_int2str(restconf_media media)
return clicon_int2str(http_media_map, media);
}
int
restconf_str2proto(char *str)
{
return clicon_str2int(http_proto_map, str);
}
const char *
restconf_proto2str(int proto)
{
return clicon_int2str(http_proto_map, proto);
}
/*! Return media_in from Content-Type, -1 if not found or unrecognized
* @note media-type syntax does not support parameters
* @see RFC7231 Sec 3.1.1.1 for media-type syntax type:

View file

@ -68,6 +68,14 @@ enum ietf_ds {
};
typedef enum ietf_ds ietf_ds_t;
/* Just used in native */
enum restconf_http_proto{
HTTP_10,
HTTP_11,
HTTP_2
};
typedef enum restconf_http_proto restconf_http_proto;
/*
* Prototypes
*/
@ -75,6 +83,8 @@ int restconf_err2code(char *tag);
const char *restconf_code2reason(int code);
const restconf_media restconf_media_str2int(char *media);
const char *restconf_media_int2str(restconf_media media);
int restconf_str2proto(char *str);
const char *restconf_proto2str(int proto);
restconf_media restconf_content_type(clicon_handle h);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
int restconf_terminate(clicon_handle h);

View file

@ -151,6 +151,7 @@
/* clicon */
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
@ -158,6 +159,12 @@
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h> /* XXX inline this / use SSL directly */
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
/* nghttp2 */
#include <nghttp2/nghttp2.h>
#endif
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
@ -336,6 +343,7 @@ openssl_cat_log_cb(void *handle,
return 0;
}
#ifdef HAVE_LIBEVHTP
static char*
evhtp_method2str(enum htp_method m)
{
@ -399,17 +407,18 @@ evhtp_method2str(enum htp_method m)
}
}
static int
print_header(evhtp_header_t *header,
void *arg)
evhtp_print_header(evhtp_header_t *header,
void *arg)
{
clicon_debug(1, "%s %s %s", __FUNCTION__, header->key, header->val);
return 0;
}
static int
query_iterator(evhtp_header_t *hdr,
void *arg)
evhtp_query_iterator(evhtp_header_t *hdr,
void *arg)
{
cvec *qvec = (cvec *)arg;
char *key;
@ -436,8 +445,8 @@ query_iterator(evhtp_header_t *hdr,
* Example: Host -> HTTP_HOST
*/
static int
convert_fcgi(evhtp_header_t *hdr,
void *arg)
evhtp_convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
@ -520,7 +529,7 @@ evhtp_params_set(clicon_handle h,
* that would mean double parsing,...
*/
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){
if (evhtp_kvs_for_each(uri->query, evhtp_query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
goto done;
}
@ -558,7 +567,7 @@ evhtp_params_set(clicon_handle h,
/* Translate all http headers by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
if (evhtp_headers_for_each(req->headers_in, convert_fcgi, h) < 0)
if (evhtp_headers_for_each(req->headers_in, evhtp_convert_fcgi, h) < 0)
goto done;
retval = 1;
done:
@ -593,7 +602,6 @@ restconf_path_root(evhtp_request_t *req,
{
int retval = -1;
clicon_handle h;
evhtp_connection_t *conn;
int ret;
cvec *qvec = NULL;
@ -602,13 +610,9 @@ restconf_path_root(evhtp_request_t *req,
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
if ((conn = req->conn) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "req->conn is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
@ -652,7 +656,6 @@ restconf_path_wellknown(evhtp_request_t *req,
{
int retval = -1;
clicon_handle h;
evhtp_connection_t *conn;
int ret;
clicon_debug(1, "------------");
@ -660,13 +663,9 @@ restconf_path_wellknown(evhtp_request_t *req,
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
if ((conn = req->conn) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "req->conn is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
@ -688,6 +687,7 @@ restconf_path_wellknown(evhtp_request_t *req,
}
return; /* void */
}
#endif /* HAVE_LIBEVHTP */
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
@ -720,8 +720,8 @@ init_openssl(void)
* used for the certificate chain verification.
*/
static int
restconf_verify_certs(int preverify_ok,
evhtp_x509_store_ctx_t *store)
restconf_verify_certs(int preverify_ok,
X509_STORE_CTX *store)
{
char buf[256];
X509 *err_cert;
@ -768,7 +768,8 @@ dump_alpn_proto_list(const unsigned char *in,
char *str;
inp = (unsigned char*)in;
while ((len = *inp) != 0) {
while ((inp-in) < inlen) {
len = *inp;
inp++;
if ((str = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
@ -798,23 +799,33 @@ alpn_select_proto_cb(SSL *ssl,
{
unsigned char *inp;
unsigned char len;
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 ((len = *inp) != 0) {
while ((inp-in) < inlen) {
len = *inp;
inp++;
if (len == 8 && strncmp((char*)inp, "http/1.1", len) == 0)
// if (len == 2 && strncmp((char*)inp, "h2", len) == 0)
break;
if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){
*outlen = len;
*out = inp;
pref = 10;
}
#ifdef HAVE_LIBNGHTTP2
/* Higher pref than http/1.1 */
else if (pref < 20 && len == 2 && strncmp((char*)inp, "h2", len) == 0){
*outlen = len;
*out = inp;
pref = 20;
}
#endif
inp += len;
}
if (len == 0)
if (pref == 0)
return SSL_TLSEXT_ERR_NOACK;
*outlen = len;
*out = inp;
return SSL_TLSEXT_ERR_OK;
}
@ -912,13 +923,12 @@ restconf_ssl_context_configure(clixon_handle h,
}
/*! Free clixon/cbuf resources related to an evhtp connection
* @param[in] rc restconf connection
*/
static int
restconf_conn_free(evhtp_connection_t *conn)
restconf_conn_free(restconf_conn_h *rc)
{
restconf_conn_h *rc;
if ((rc = conn->arg) != NULL){
if (rc != NULL){
if (rc->rc_outp_hdrs)
cvec_free(rc->rc_outp_hdrs);
if (rc->rc_outp_buf)
@ -933,31 +943,33 @@ restconf_conn_free(evhtp_connection_t *conn)
* and always use this function, but it is difficult.
*/
static int
close_ssl_evhtp_socket(int s,
evhtp_connection_t *conn,
int shutdown)
close_ssl_socket(restconf_conn_h *rc,
int shutdown)
{
int retval = -1;
int ret;
SSL *ssl;
ssl = conn->ssl;
clicon_debug(1, "%s conn-free (%p) 1", __FUNCTION__, conn);
restconf_conn_free(conn);
evhtp_connection_free(conn); /* evhtp */
if (ssl != NULL){
if (shutdown && (ret = SSL_shutdown(ssl)) < 0){
int e = SSL_get_error(ssl, ret);
int ret;
#ifdef HAVE_LIBEVHTP
evhtp_connection_t *evconn;
evconn = (evhtp_connection_t *)rc->rc_arg;
clicon_debug(1, "%s evconn-free (%p) 1", __FUNCTION__, evconn);
evhtp_connection_free(evconn); /* evhtp */
#endif /* HAVE_LIBEVHTP */
if (rc->rc_ssl != NULL){
if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){
int e = SSL_get_error(rc->rc_ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(ssl);
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
}
if (close(s) < 0){
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(s, restconf_connection);
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
restconf_conn_free(rc);
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
@ -1019,89 +1031,93 @@ send_badrequest(clicon_handle h,
* OR evhtp returns 0 with no reply, then this is assumed to mean read more data from the socket.
*/
static int
restconf_connection(int s,
restconf_connection(int s,
void *arg)
{
int retval = -1;
evhtp_connection_t *conn = NULL;
restconf_conn_h *rc = NULL;
ssize_t n;
char buf[BUFSIZ]; /* from stdio.h, typically 8K */
clicon_handle h;
int readmore = 1;
restconf_conn_h *rc;
#ifdef HAVE_LIBEVHTP
clicon_handle h;
evhtp_connection_t *evconn = NULL;
#endif
clicon_debug(1, "%s", __FUNCTION__);
if ((conn = (evhtp_connection_t*)arg) == NULL){
clicon_debug(1, "%s %d", __FUNCTION__, s);
if ((rc = (restconf_conn_h*)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
h = (clicon_handle*)conn->htp->arg;
assert(s == rc->rc_s);
while (readmore) {
clicon_debug(1, "%s readmore", __FUNCTION__);
readmore = 0;
/* Example: curl -Ssik -u wilma:bar -X GET https://localhost/restconf/data/example:x */
if (conn->ssl){
if (rc->rc_ssl){
/* Non-ssl gets n == 0 here!
curl -Ssik --key /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.key --cert /var/tmp/./test_restconf_ssl_certs.sh/certs/limited.crt -X GET https://localhost/restconf/data/example:x
*/
if ((n = SSL_read(conn->ssl, buf, sizeof(buf))) < 0){
if ((n = SSL_read(rc->rc_ssl, buf, sizeof(buf))) < 0){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
}
else{
if ((n = read(conn->sock, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */
if ((n = read(rc->rc_s, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */
if (errno == ECONNRESET) {/* Connection reset by peer */
close(conn->sock);
restconf_conn_free(conn);
clixon_event_unreg_fd(conn->sock, restconf_connection);
clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s);
close(rc->rc_s);
restconf_conn_free(rc);
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
goto ok; /* Close socket and ssl */
}
clicon_err(OE_XML, errno, "read");
goto done;
}
}
clicon_debug(1, "%s read:%ld", __FUNCTION__, n);
if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (close_ssl_evhtp_socket(s, conn, 1) < 0)
if (close_ssl_socket(rc, 1) < 0)
goto done;
goto ok;
}
/* parse incoming packet
#ifdef HAVE_LIBEVHTP
h = rc->rc_h;
/* parse incoming packet using evhtp
* signature:
*/
if (connection_parse_nobev(buf, n, conn) < 0){
evconn = (evhtp_connection_t*)rc->rc_arg;
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, s, conn->ssl, "application/yang-data+xml",
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(conn->ssl);
if (close(s) < 0){
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(s, restconf_connection);
conn->ssl = NULL;
clicon_debug(1, "%s conn-free (%p) 2", __FUNCTION__, conn);
restconf_conn_free(conn);
evhtp_connection_free(conn);
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 (conn->bev != NULL){
if (evconn->bev != NULL){
struct evbuffer *ev;
size_t buflen0;
size_t buflen1;
char *buf = NULL;
if ((rc = conn->arg) == NULL){
clicon_err(OE_RESTCONF, EFAULT, "Internal error: restconf-conn-h is NULL: shouldnt happen");
goto done;
}
if ((ev = bufferevent_get_output(conn->bev)) != NULL){
if ((ev = bufferevent_get_output(evconn->bev)) != NULL){
buflen0 = evbuffer_get_length(ev);
buflen1 = buflen0 - rc->rc_bufferevent_output_offset;
if (buflen1 > 0){
@ -1126,17 +1142,19 @@ restconf_connection(int s,
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), conn->sock, conn->ssl) < 0)
if (buf_write(cbuf_get(rc->rc_outp_buf), cbuf_len(rc->rc_outp_buf),
rc->rc_s, rc->rc_ssl) < 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, s, conn->ssl, "application/yang-data+xml",
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;
}
#endif /* HAVE_LIBEVHTP */
} /* while readmore */
ok:
retval = 0;
@ -1211,6 +1229,89 @@ restconf_checkcert_file(cxobj *xrestconf,
return retval;
}
/*! Check ALPN result
* @proto[out] proto
* @retval 1 OK with proto set
* @retval 0 Fail, ALPN null or not recognized
* @retval -1 Error
*/
static int
ssl_alpn_check(clicon_handle h,
const unsigned char *alpn,
unsigned int alpnlen,
restconf_conn_h *rc,
restconf_http_proto *proto)
{
int retval = -1;
int ret;
cbuf *cberr = NULL;
clicon_debug(1, "%s", __FUNCTION__);
/* Alternatively, call restconf_str2proto but alpn is not a proper string */
if (alpn && alpnlen == 8 && memcmp("http/1.1", alpn, 8) == 0){
*proto = HTTP_11;
retval = 1; /* http/1.1 */
goto done;
}
#ifdef HAVE_LIBNGHTTP2
else if (alpn && alpnlen == 2 && memcmp("h2", alpn, 2) == 0){
*proto = HTTP_2;
retval = 1; /* http/2 */
goto done;
}
#endif
else {
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (alpn != NULL){
cprintf(cberr, "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>ALPN: protocol not recognized: %s</error-message></error></errors>", alpn);
clicon_log(LOG_NOTICE, "Warning: %s", cbuf_get(cberr));
if (send_badrequest(h, rc->rc_s, rc->rc_ssl,
"application/yang-data+xml",
cbuf_get(cberr)) < 0)
goto done;
}
else{
/* XXX Sending badrequest here gives a segv in SSL_shutdown() later or a SIGPIPE here */
clicon_log(LOG_NOTICE, "Warning: ALPN: No protocol selected");
}
restconf_conn_free(rc);
#ifdef HAVE_LIBEVHTP
{
evhtp_connection_t *evconn;
if ((evconn = (evhtp_connection_t *)rc->rc_arg) != NULL)
evhtp_connection_free(evconn); /* evhtp */
}
#endif /* HAVE_LIBEVHTP */
if (rc->rc_ssl){
/* nmap ssl-known-key SEGV at s->method->ssl_shutdown(s);
* OR OpenSSL error: : SSL_shutdown, err: SSL_ERROR_SYSCALL(5)
*/
if ((ret = SSL_shutdown(rc->rc_ssl)) < 0){
int e = SSL_get_error(rc->rc_ssl, ret);
if (e == SSL_ERROR_SYSCALL){
clicon_log(LOG_NOTICE, "Warning: SSL_shutdown SSL_ERROR_SYSCALL");
/* Continue */
}
else {
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
}
SSL_free(rc->rc_ssl);
}
}
retval = 0; /* ALPN not OK */
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
return retval;
}
/*! Accept new socket client
* @param[in] fd Socket (unix or ip)
* @param[in] arg typecast clicon_handle
@ -1220,31 +1321,33 @@ static int
restconf_accept_client(int fd,
void *arg)
{
int retval = -1;
restconf_socket *rsock = (restconf_socket *)arg;
int retval = -1;
restconf_socket *rsock;
restconf_native_handle *rh = NULL;
restconf_conn_h *rc = NULL;
clicon_handle h;
int s;
struct sockaddr from = {0,};
socklen_t len;
char *name = NULL;
SSL *ssl = NULL; /* structure for ssl connection */
int ret;
evhtp_t *evhtp = NULL;
evhtp_connection_t *conn;
int e;
int er;
int readmore;
X509 *peercert;
const unsigned char *alpn = NULL;
unsigned int alpnlen = 0;
restconf_conn_h *rc = NULL;
clicon_handle h;
int s;
struct sockaddr from = {0,};
socklen_t len;
char *name = NULL;
int ret;
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 */
clicon_debug(1, "%s %d", __FUNCTION__, fd);
if (rsock == NULL){
if ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
clicon_debug(1, "%s type:%s addr:%s port:%hu", __FUNCTION__,
rsock->rs_addrtype,
rsock->rs_addrstr,
rsock->rs_port);
h = rsock->rs_h;
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
@ -1255,24 +1358,38 @@ restconf_accept_client(int fd,
clicon_err(OE_UNIX, errno, "accept");
goto done;
}
evhtp = rh->rh_evhtp;
if ((conn = evhtp_connection_new_server(evhtp, s)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_connection_new_server");
/*
* Register callbacks for actual data socket
*/
if ((rc = (restconf_conn_h*)malloc(sizeof(restconf_conn_h))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
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;
}
clicon_debug(1, "%s conn-new (%p)", __FUNCTION__, conn);
if (rsock->rs_ssl){
if ((ssl = SSL_new(rh->rh_ctx)) == NULL){
if ((rc->rc_ssl = SSL_new(rh->rh_ctx)) == NULL){
clicon_err(OE_SSL, 0, "SSL_new");
goto done;
}
clicon_debug(1, "%s SSL_new(%p)", __FUNCTION__, ssl);
clicon_debug(1, "%s SSL_new(%p)", __FUNCTION__, rc->rc_ssl);
/* CCL_CTX_set_verify already set, need not call SSL_set_verify again for this server
*/
/* X509_CHECK_FLAG_NO_WILDCARDS disables wildcard expansion */
SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_WILDCARDS);
SSL_set_hostflags(rc->rc_ssl, X509_CHECK_FLAG_NO_WILDCARDS);
#if 0
/* Enable this if you want to restrict client certs to a specific set.
/* XXX This code is kept for the time being just for reference, it does not belong here.
* If you want to restrict client certs to a specific set.
* Otherwise this is done in restcon ca-auth callback and ultimately NACM
* SSL_set1_host() sets the expected DNS hostname to name
C = SE
@ -1282,17 +1399,16 @@ restconf_accept_client(int fd,
CN = ca <---
emailAddress = olof@hagsand.se
*/
if (SSL_set1_host(ssl, "andy") != 1) { /* for peer cert */
if (SSL_set1_host(rc->rc_ssl, "andy") != 1) { /* for peer cert */
clicon_err(OE_SSL, 0, "SSL_set1_host");
goto done;
}
if (SSL_add1_host(ssl, "olof") != 1) { /* for peer cert */
if (SSL_add1_host(rc->rc_ssl, "olof") != 1) { /* for peer cert */
clicon_err(OE_SSL, 0, "SSL_set1_host");
goto done;
}
#endif
conn->ssl = ssl; /* evhtp */
if (SSL_set_fd(ssl, s) != 1){
if (SSL_set_fd(rc->rc_ssl, rc->rc_s) != 1){
clicon_err(OE_SSL, 0, "SSL_set_fd");
goto done;
}
@ -1302,30 +1418,25 @@ restconf_accept_client(int fd,
/* 1: OK, -1 fatal, 0: TLS/SSL handshake was not successful
* Both error cases: Call SSL_get_error() with the return value ret
*/
if ((ret = SSL_accept(ssl)) != 1) {
if ((ret = SSL_accept(rc->rc_ssl)) != 1) {
clicon_debug(1, "%s SSL_accept() ret:%d errno:%d", __FUNCTION__, ret, er=errno);
e = SSL_get_error(ssl, ret);
e = SSL_get_error(rc->rc_ssl, ret);
switch (e){
case SSL_ERROR_SSL: /* 1 */
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
#if 0
/* XXX sending a bad request here may crash
* eg when run nmap --script ssl*
*/
if (send_badrequest(h, s, NULL, "application/yang-data+xml",
#if 1
if (send_badrequest(h, rc->rc_s, NULL, "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 plain HTTP request was sent to HTTPS port</error-message></error></errors>") < 0)
goto done;
#endif
SSL_free(ssl);
if (close(s) < 0){
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(s, restconf_connection);
conn->ssl = NULL;
clicon_debug(1, "%s conn-free (%p) 3", __FUNCTION__, conn);
restconf_conn_free(conn);
evhtp_connection_free(conn); /* evhtp */
restconf_conn_free(rc);
goto ok;
break;
case SSL_ERROR_SYSCALL: /* 5 */
@ -1335,7 +1446,7 @@ 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_evhtp_socket(s, conn, 0) < 0)
if (close_ssl_socket(rc, 0) < 0)
goto done;
goto ok;
break;
@ -1365,7 +1476,7 @@ restconf_accept_client(int fd,
goto done;
break;
}
}
} /* SSL_accept */
} /* while(readmore) */
/* For client-cert authentication, check if any certs are present,
* if not, send bad request
@ -1373,53 +1484,43 @@ restconf_accept_client(int fd,
* but then SSL_accept fails.
*/
if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){
if ((peercert = SSL_get_peer_certificate(ssl)) != NULL){
if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){
X509_free(peercert);
}
else { /* Get certificates (if available) */
if (send_badrequest(h, s, 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)
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>Peer certificate required</error-message></error></errors>") < 0)
goto done;
restconf_conn_free(conn);
evhtp_connection_free(conn); /* evhtp */
if (ssl){
if ((ret = SSL_shutdown(ssl)) < 0){
int e = SSL_get_error(ssl, ret);
restconf_conn_free(rc);
if (rc->rc_ssl){
if ((ret = SSL_shutdown(rc->rc_ssl)) < 0){
int e = SSL_get_error(rc->rc_ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(ssl);
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
}
goto ok;
}
}
/* Sets data and len to point to the client's requested protocol for this connection. */
SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen);
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(ssl, &alpn, &alpnlen);
SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen);
}
clicon_debug(1, "%s ALPN: %d %s", __FUNCTION__, alpnlen, alpn);
if (alpn == NULL || alpnlen != 8 || memcmp("http/1.1", alpn, 8) != 0) {
clicon_log(LOG_NOTICE, "------Warning1 Protocol http/1.1 not selected: %s", alpn);
if (send_badrequest(h, s, 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(conn);
evhtp_connection_free(conn); /* evhtp */
if (ssl){
if ((ret = SSL_shutdown(ssl)) < 0){
int e = SSL_get_error(ssl, ret);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(ssl);
}
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));
/* 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,...
*/
if (SSL_get_verify_result(ssl) == X509_V_OK) { /* for peer cert */
const char *peername = SSL_get0_peername(ssl);
if (SSL_get_verify_result(rc->rc_ssl) == X509_V_OK) { /* for peer cert */
const char *peername = SSL_get0_peername(rc->rc_ssl);
if (peername != NULL) {
/* Name checks were in scope and matched the peername */
@ -1428,27 +1529,33 @@ restconf_accept_client(int fd,
}
#if 0 /* debug */
if (clicon_debug_get())
restconf_listcerts(ssl);
restconf_listcerts(rc->rc_ssl);
#endif
} /* if ssl */
rc->rc_proto = proto;
switch (rc->rc_proto){
#ifdef HAVE_LIBEVHTP
case HTTP_10:
case HTTP_11:{
evhtp_connection_t *evconn;
/* Create evhtp-specific struct */
if ((evconn = evhtp_connection_new_server(rh->rh_evhtp, rc->rc_s)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_connection_new_server");
goto done;
}
/* Mutual pointers, from generic rc to evhtp specific and from evhtp conn to generic
*/
rc->rc_arg = evconn; /* Generic to specific */
evconn->arg = rc; /* Specific to generic */
evconn->ssl = rc->rc_ssl; /* evhtp */
}
/*
* Register callbacks for actual data socket
*/
if ((rc = (restconf_conn_h*)malloc(sizeof(restconf_conn_h))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(rc, 0, sizeof(restconf_conn_h));
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;
}
conn->arg = rc;
if (clixon_event_reg_fd(s, restconf_connection, (void*)conn, "restconf client socket") < 0)
break;
#endif /* HAVE_LIBEVHTP */
case HTTP_2:
default:
break;
} /* switch proto */
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
goto done;
ok:
retval = 0;
@ -1463,7 +1570,7 @@ static int
restconf_native_terminate(clicon_handle h)
{
restconf_native_handle *rh;
restconf_socket *rsock;
restconf_socket *rsock;
clicon_debug(1, "%s", __FUNCTION__);
if ((rh = restconf_native_handle_get(h)) != NULL){
@ -1471,15 +1578,22 @@ restconf_native_terminate(clicon_handle h)
clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client);
close(rsock->rs_ss);
DELQ(rsock, rh->rh_sockets, restconf_socket *);
if (rsock->rs_addrstr)
free(rsock->rs_addrstr);
if (rsock->rs_addrtype)
free(rsock->rs_addrtype);
free(rsock);
}
if (rh->rh_ctx)
SSL_CTX_free(rh->rh_ctx);
#ifdef HAVE_LIBEVHTP
if (rh->rh_evhtp){
if (rh->rh_evhtp->evbase)
event_base_free(rh->rh_evhtp->evbase);
evhtp_free(rh->rh_evhtp);
}
#endif /* HAVE_LIBEVHTP */
free(rh);
}
EVP_cleanup();
@ -1596,6 +1710,7 @@ openssl_init_socket(clicon_handle h,
}
/*
* Create per-socket openssl handle
* See restconf_native_terminate for freeing
*/
if ((rsock = malloc(sizeof *rsock)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
@ -1605,6 +1720,15 @@ openssl_init_socket(clicon_handle h,
rsock->rs_h = h;
rsock->rs_ss = ss;
rsock->rs_ssl = ssl;
if ((rsock->rs_addrstr = strdup(address)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if ((rsock->rs_addrtype = strdup(addrtype)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
rsock->rs_port = port;
INSQ(rsock, rh->rh_sockets);
/* ss is a server socket that the clients connect to. The callback
@ -1643,8 +1767,10 @@ restconf_openssl_init(clicon_handle h,
cxobj **vec = NULL;
size_t veclen;
int i;
#ifdef HAVE_LIBEVHTP
evhtp_t *evhtp = NULL;
struct event_base *evbase = NULL;
#endif /* HAVE_LIBEVHTP */
clicon_debug(1, "%s", __FUNCTION__);
/* flag used for sanity of certs */
@ -1699,6 +1825,7 @@ restconf_openssl_init(clicon_handle h,
}
rh = restconf_native_handle_get(h);
rh->rh_ctx = ctx;
#ifdef HAVE_LIBEVHTP
/* evhtp stuff */ /* XXX move this to global level */
if ((evbase = event_base_new()) == NULL){
clicon_err(OE_UNIX, errno, "event_base_new");
@ -1719,6 +1846,7 @@ restconf_openssl_init(clicon_handle h,
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
}
#endif /* HAVE_LIBEVHTP */
/* get the list of socket config-data */
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
goto done;

View file

@ -287,7 +287,10 @@ api_data_write(clicon_handle h,
cxobj *xfrom;
cxobj *xac;
xfrom = (api_path && strcmp(api_path,"/"))?xml_parent(xbot):xbot; // XXX xbot is /config has NULL parent
if (api_path && (strcmp(api_path, "/") != 0))
xfrom = xml_parent(xbot);
else
xfrom = xbot; // XXX xbot is /config has NULL parent
if (xml_copy_one(xfrom, xdata0) < 0)
goto done;
xa = NULL;

View file

@ -63,10 +63,15 @@ extern "C" {
* Per connection request
*/
typedef struct {
// 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 */
// 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 */
void *rc_arg; /* Specific connection pointer, eg evhtp conn struct */
} restconf_conn_h;
/* Restconf request handle
@ -77,6 +82,10 @@ typedef struct {
clicon_handle rs_h; /* Clixon handle */
int rs_ss; /* Server socket (ready for accept) */
int rs_ssl; /* 0: Not SSL socket, 1:SSL socket */
char *rs_addrtype; /* Address type according to ietf-inet-types:
eg inet:ipv4-address or inet:ipv6-address */
char *rs_addrstr; /* Address as string, eg 127.0.0.1, ::1 */
uint16_t rs_port; /* Protocol port */
} restconf_socket;
/* Restconf handle
@ -84,8 +93,10 @@ typedef struct {
*/
typedef struct {
SSL_CTX *rh_ctx; /* SSL context */
evhtp_t *rh_evhtp; /* Evhtp struct */
restconf_socket *rh_sockets; /* List of restconf server (ready for accept) sockets */
#ifdef HAVE_LIBEVHTP
evhtp_t *rh_evhtp; /* Evhtp struct */
#endif
} restconf_native_handle;
/*

166
configure vendored
View file

@ -718,6 +718,8 @@ with_cligen
enable_optyangs
enable_publish
with_restconf
enable_evhtp
enable_nghttp2
with_configfile
with_libxml2
with_yang_installdir
@ -1365,6 +1367,10 @@ Optional Features:
in clixon install, default: no
--enable-publish Enable publish of notification streams using SSE and
curl
--disable-evhtp Disable evhtp for native restconf http/1, default:
yes
--enable-nghttp2 Enable nghttp2 for native restconf http/2, default:
no
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@ -3352,7 +3358,7 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
# Set to native or fcgi -> compile apps/restconf
# Home dir for web user
# Home dir for web user, such as nginx fcgi sockets
wwwdir=/www-data
@ -5045,6 +5051,11 @@ fi
$as_echo "#define WITH_RESTCONF_FCGI 1" >>confdefs.h
# For c-code that cant use strings
cat >>confdefs.h <<_ACEOF
#define WWWDIR "$wwwdir"
_ACEOF
elif test "x${with_restconf}" == xnative; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for OPENSSL_init_ssl in -lssl" >&5
$as_echo_n "checking for OPENSSL_init_ssl in -lssl... " >&6; }
@ -5140,7 +5151,42 @@ else
as_fn_error $? "libcrypto missing" "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for event_init in -levent" >&5
# Check if evhtp is enabled for http/1
# Check whether --enable-evhtp was given.
if test "${enable_evhtp+set}" = set; then :
enableval=$enable_evhtp;
if test "$enableval" = no; then
ac_enable_evhtp=no
else
ac_enable_evhtp=yes
fi
else
ac_enable_evhtp=yes
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: checking evhtp is enabled: $ac_enable_evhtp" >&5
$as_echo "checking evhtp is enabled: $ac_enable_evhtp" >&6; }
if test "$ac_enable_evhtp" = "yes"; then
for ac_header in evhtp/evhtp.h
do :
ac_fn_c_check_header_compile "$LINENO" "evhtp/evhtp.h" "ac_cv_header_evhtp_evhtp_h" "$ac_includes_default
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
"
if test "x$ac_cv_header_evhtp_evhtp_h" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_EVHTP_EVHTP_H 1
_ACEOF
else
as_fn_error $? "evhtp header missing. See https://github.com/clicon/libevhtp" "$LINENO" 5
fi
done
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for event_init in -levent" >&5
$as_echo_n "checking for event_init in -levent... " >&6; }
if ${ac_cv_lib_event_event_init+:} false; then :
$as_echo_n "(cached) " >&6
@ -5187,25 +5233,7 @@ else
as_fn_error $? "libevent missing" "$LINENO" 5
fi
for ac_header in evhtp/evhtp.h
do :
ac_fn_c_check_header_compile "$LINENO" "evhtp/evhtp.h" "ac_cv_header_evhtp_evhtp_h" "$ac_includes_default
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
"
if test "x$ac_cv_header_evhtp_evhtp_h" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_EVHTP_EVHTP_H 1
_ACEOF
else
as_fn_error $? "evhtp header missing. See https://github.com/clicon/libevhtp" "$LINENO" 5
fi
done
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for evhtp_new in -levhtp" >&5
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for evhtp_new in -levhtp" >&5
$as_echo_n "checking for evhtp_new in -levhtp... " >&6; }
if ${ac_cv_lib_evhtp_evhtp_new+:} false; then :
$as_echo_n "(cached) " >&6
@ -5252,6 +5280,87 @@ else
as_fn_error $? "libevhtp missing" "$LINENO" 5
fi
fi
# Check if nghttp2 is enabled for http/2
# Check whether --enable-nghttp2 was given.
if test "${enable_nghttp2+set}" = set; then :
enableval=$enable_nghttp2;
if test "$enableval" = no; then
ac_enable_nghttp2=no
else
ac_enable_nghttp2=yes
fi
else
ac_enable_nghttp2=no
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: checking nghttp2 is enabled: $ac_enable_nghttp2" >&5
$as_echo "checking nghttp2 is enabled: $ac_enable_nghttp2" >&6; }
if test "$ac_enable_nghttp2" = "yes"; then
for ac_header in nghttp2/nghttp2.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "nghttp2/nghttp2.h" "ac_cv_header_nghttp2_nghttp2_h" "$ac_includes_default"
if test "x$ac_cv_header_nghttp2_nghttp2_h" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_NGHTTP2_NGHTTP2_H 1
_ACEOF
else
as_fn_error $? "nghttp2 missing" "$LINENO" 5
fi
done
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for nghttp2_session_server_new in -lnghttp2" >&5
$as_echo_n "checking for nghttp2_session_server_new in -lnghttp2... " >&6; }
if ${ac_cv_lib_nghttp2_nghttp2_session_server_new+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-lnghttp2 $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char nghttp2_session_server_new ();
int
main ()
{
return nghttp2_session_server_new ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_nghttp2_nghttp2_session_server_new=yes
else
ac_cv_lib_nghttp2_nghttp2_session_server_new=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_nghttp2_nghttp2_session_server_new" >&5
$as_echo "$ac_cv_lib_nghttp2_nghttp2_session_server_new" >&6; }
if test "x$ac_cv_lib_nghttp2_nghttp2_session_server_new" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_LIBNGHTTP2 1
_ACEOF
LIBS="-lnghttp2 $LIBS"
else
as_fn_error $? "nghttp2 missing" "$LINENO" 5
fi
fi
$as_echo "#define WITH_RESTCONF_NATIVE 1" >>confdefs.h
# For c-code that cant use strings
@ -5290,21 +5399,6 @@ if test "${with_restconf+set}" = set; then :
fi
# Common actions for all restconf packages
if test "x${with_restconf}" != "x"; then
cat >>confdefs.h <<_ACEOF
#define WWWDIR "$wwwdir"
_ACEOF
else
cat >>confdefs.h <<_ACEOF
#define WWWDIR ""
_ACEOF
fi
# Set default config file location
CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml

View file

@ -218,15 +218,42 @@ if test "x${with_restconf}" == xfcgi; then
elif test "x${with_restconf}" == xnative; then
AC_CHECK_LIB(ssl, OPENSSL_init_ssl ,, AC_MSG_ERROR([libssl missing]))
AC_CHECK_LIB(crypto, CRYPTO_new_ex_data, , AC_MSG_ERROR([libcrypto missing]))
AC_CHECK_LIB(event, event_init,, AC_MSG_ERROR([libevent missing]))
AC_CHECK_HEADERS(evhtp/evhtp.h,
[],
AC_MSG_ERROR([evhtp header missing. See https://github.com/clicon/libevhtp]),
[AC_INCLUDES_DEFAULT[
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
]])
AC_CHECK_LIB(evhtp, evhtp_new,, AC_MSG_ERROR([libevhtp missing]),[-levent -lssl -lcrypto])
# Check if evhtp is enabled for http/1
AC_ARG_ENABLE(evhtp, AS_HELP_STRING([--disable-evhtp],[Disable evhtp for native restconf http/1, default: yes]),[
if test "$enableval" = no; then
ac_enable_evhtp=no
else
ac_enable_evhtp=yes
fi
],
[ ac_enable_evhtp=yes])
AC_MSG_RESULT(checking evhtp is enabled: $ac_enable_evhtp)
if test "$ac_enable_evhtp" = "yes"; then
AC_CHECK_HEADERS(evhtp/evhtp.h,
[],
AC_MSG_ERROR([evhtp header missing. See https://github.com/clicon/libevhtp]),
[AC_INCLUDES_DEFAULT[
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
]])
AC_CHECK_LIB(event, event_init,, AC_MSG_ERROR([libevent missing]))
AC_CHECK_LIB(evhtp, evhtp_new,, AC_MSG_ERROR([libevhtp missing]),[-levent -lssl -lcrypto])
fi
# Check if nghttp2 is enabled for http/2
AC_ARG_ENABLE(nghttp2, AS_HELP_STRING([--enable-nghttp2],[Enable nghttp2 for native restconf http/2, default: no]),[
if test "$enableval" = no; then
ac_enable_nghttp2=no
else
ac_enable_nghttp2=yes
fi
],
[ ac_enable_nghttp2=no])
AC_MSG_RESULT(checking nghttp2 is enabled: $ac_enable_nghttp2)
if test "$ac_enable_nghttp2" = "yes"; then
AC_CHECK_HEADERS(nghttp2/nghttp2.h,[], AC_MSG_ERROR([nghttp2 missing]))
AC_CHECK_LIB(nghttp2, nghttp2_session_server_new,, AC_MSG_ERROR([nghttp2 missing]))
fi
AC_DEFINE(WITH_RESTCONF_NATIVE, 1, [Use native restconf mode]) # For c-code that cant use strings
elif test "x${with_restconf}" == xno; then
# Cant get around "no" as an answer for --without-restconf that is reset here to undefined

View file

@ -60,6 +60,9 @@
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
/* Define to 1 if you have the `nghttp2' library (-lnghttp2). */
#undef HAVE_LIBNGHTTP2
/* Define to 1 if you have the `socket' library (-lsocket). */
#undef HAVE_LIBSOCKET
@ -72,6 +75,9 @@
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the <nghttp2/nghttp2.h> header file. */
#undef HAVE_NGHTTP2_NGHTTP2_H
/* Define to 1 if you have the `setns' function. */
#undef HAVE_SETNS
@ -147,7 +153,7 @@
/* Use native restconf mode */
#undef WITH_RESTCONF_NATIVE
/* WWW dir for restconf daemon */
/* WWW dir for fcgi stuff / nginx */
#undef WWWDIR
/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a

View file

@ -848,6 +848,14 @@ api_path2xpath(char *api_path,
clicon_err(OE_XML, EINVAL, "api_path is NULL");
goto done;
}
/* Special case: "//" is not handled proerly by uri_str2cvec
* and is an invalid api-path
*/
if (strlen(api_path)>1 && api_path[0] == '/' && api_path[1] == '/'){
if (xerr && netconf_invalid_value_xml(xerr, "application", "Invalid api-path beginning with //") < 0)
goto done;
goto fail;
}
/* Split api-path into cligen variable vector,
* dont decode since api_path2xpath_cvv takes uri encode as input */
if (uri_str2cvec(api_path, '/', '=', 0, &cvv) < 0)

View file

@ -1917,6 +1917,10 @@ xml_copy_one(cxobj *x0,
int retval = -1;
char *s;
if (x0 == NULL || x1 == NULL){
clicon_err(OE_XML, EINVAL, "x0 or x1 is NULL");
goto done;
}
xml_type_set(x1, xml_type(x0));
if ((s = xml_name(x0))) /* malloced string */
if ((xml_name_set(x1, s)) < 0)

View file

@ -195,9 +195,8 @@ function testrun()
expectpart "$(curl $CURLOPTS -X GET https://$addr:80/.well-known/host-meta 2>&1)" 35 #"wrong version number" # dependent on curl version
else # see (1) http to https port in restconf_main_native.c
new "Wrong proto=http on https port, expect bad request"
# When run nmap --script ssl* I can crash restconf if try to return a bad request here
# expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/1.1 400 Bad Request"
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer"
expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta)" 0 "HTTP/1.1 400 Bad Request"
# expectpart "$(curl $CURLOPTS -X GET http://$addr:443/.well-known/host-meta 2>&1)" 56 "Connection reset by peer"
fi
# Exact match

View file

@ -1,20 +1,18 @@
#!/usr/bin/env bash
# Restconf basic functionality also uri encoding using eth/0/0
# Note there are many variants: (1)fcgi/native, (2) http/https, (3) IPv4/IPv6, (4)local or backend-config
# (1) fcgi/native
# This is compile-time --with-restconf=fcgi or native, so either or
# - fcgi: Assume http server setup, such as nginx described in apps/restconf/README.md
# - native: test both local config and get config from backend
# (2) http/https
# - fcgi: relies on nginx has https setup
# - native: generate self-signed server certs
# (3) IPv4/IPv6 (only loopback 127.0.0.1 / ::1)
# - The tests runs through both
# - IPv6 by default disabled since docker does not support it out-of-the box
# (4) local/backend config. Native only
# - The tests runs through both (if compiled with native)
# See also test_restconf_op.sh
# See test_restconf_rpc.sh for cases when CLICON_BACKEND_RESTCONF_PROCESS is set
# NMAP ssl script testing.
# The following tests are run:
# - ssl-ccs-injection, but not deterministic, need to repeat (10 times) maybe this is wrong?
# - ssl-cert-intaddr
# - ssl-cert
# - ssl-date
# - ssl-dh-params
# - ssl-enum-ciphers
# - ssl-heartbleed
# - ssl-known-key
# - ssl-poodle
# - sslv2-drown
# - sslv2
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -62,49 +60,22 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
# Create server certs and CA
cacerts $cakey $cacert
servercerts $cakey $cacert $srvkey $srvcert
USEBACKEND=true
else
# Define default restconfig config: RESTCONFIG
RESTCONFIG=$(restconf_config none false)
USEBACKEND=false
fi
# This is a fixed 'state' implemented in routing_backend. It is assumed to be always there
state='{"clixon-example:state":{"op":\["41","42","43"\]}'
if $IPv6; then
# For backend config, create 4 sockets, all combinations IPv4/IPv6 + http/https
RESTCONFIG1=$(cat <<EOF
<restconf xmlns="http://clicon.org/restconf">
# Create a single IPv4 https socket
RESTCONFIG=$(cat <<EOF
<restconf>
<enable>true</enable>
<auth-type>none</auth-type>
<server-cert-path>$srvcert</server-cert-path>
<server-key-path>$srvkey</server-key-path>
<server-ca-cert-path>$cakey</server-ca-cert-path>
<pretty>false</pretty>
<socket><namespace>default</namespace><address>0.0.0.0</address><port>80</port><ssl>false</ssl></socket>
<socket><namespace>default</namespace><address>0.0.0.0</address><port>443</port><ssl>true</ssl></socket>
<socket><namespace>default</namespace><address>::</address><port>80</port><ssl>false</ssl></socket>
<socket><namespace>default</namespace><address>::</address><port>443</port><ssl>true</ssl></socket>
</restconf>
EOF
)
else
# For backend config, create 2 sockets, all combinations IPv4 + http/https
RESTCONFIG1=$(cat <<EOF
<restconf xmlns="http://clicon.org/restconf">
<enable>true</enable>
<auth-type>none</auth-type>
<server-cert-path>$srvcert</server-cert-path>
<server-key-path>$srvkey</server-key-path>
<server-ca-cert-path>$cakey</server-ca-cert-path>
<pretty>false</pretty>
<socket><namespace>default</namespace><address>0.0.0.0</address><port>80</port><ssl>false</ssl></socket>
<socket><namespace>default</namespace><address>0.0.0.0</address><port>443</port><ssl>true</ssl></socket>
</restconf>
EOF
)
fi
# Clixon config
cat <<EOF > $cfg
@ -124,8 +95,8 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_BACKEND_RESTCONF_PROCESS>$USEBACKEND</CLICON_BACKEND_RESTCONF_PROCESS>
$RESTCONFIG <!-- only fcgi -->
<CLICON_BACKEND_RESTCONF_PROCESS>false</CLICON_BACKEND_RESTCONF_PROCESS>
$RESTCONFIG
</clixon-config>
EOF
@ -145,11 +116,11 @@ fi
new "wait backend"
wait_backend
new "netconf edit config"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config>$RESTCONFIG1</config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
#new "netconf edit config"
#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><target><candidate/></target><config>$RESTCONFIG</config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
#new "netconf commit"
#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
if [ $RC -ne 0 ]; then
new "kill old restconf daemon"
@ -158,7 +129,7 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon"
# inline of start_restconf, cant make quotes to work
echo "sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R <xml>"
sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg -R "$RESTCONFIG1" &
sudo -u $wwwstartuser -s $clixon_restconf $RCLOG -D $DBG -f $cfg &
if [ $? -ne 0 ]; then
err1 "expected 0" "$?"
fi
@ -167,10 +138,54 @@ fi
new "wait restconf"
wait_restconf
sleep 1 # Sometimes nmap test fails with no reply from server, _maybe_ this helps?
new "nmap test"
# Try 10 times, dont know why this is undeterministic?
let i=0;
new "nmap ssl-ccs-injection$i"
result=$(nmap --script ssl-ccs-injection -p 443 127.0.0.1)
# echo "result:$result"
while [[ "$result" = *"No reply from server"* ]]; do
if [ $i -ge 10 ]; then
err "ssl-ccs-injection"
fi
sleep 1
let i++;
new "nmap ssl-ccs-injection$i"
result=$(nmap --script ssl-ccs-injection -p 443 127.0.0.1)
# echo "result:$result"
done
expectpart "$(nmap --script ssl* -p 443 127.0.0.1)" 0 "443/tcp open https" "least strength: A" "Nmap done: 1 IP address (1 host up) scanned in" --not-- "No reply from server" "TLSv1.0:" "TLSv1.1:"
new "nmap ssl-cert-intaddr"
expectpart "$(nmap --script ssl-cert-intaddr -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap ssl-cert"
expectpart "$(nmap --script ssl-cert -p 443 127.0.0.1)" 0 "443/tcp open https" "| ssl-cert: Subject: commonName=www.clicon.org/organizationName=Clixon/countryName=SE"
new "nmap ssl-date"
expectpart "$(nmap --script ssl-date -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap ssl-dh-params"
expectpart "$(nmap --script ssl-dh-params -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap ssl-enum-ciphers"
expectpart "$(nmap --script ssl-enum-ciphers -p 443 127.0.0.1)" 0 "443/tcp open https" "least strength: A" "TLSv1.2" --not-- "No reply from server" "TLSv1.0:" "TLSv1.1:"
new "nmap ssl-heartbleed"
expectpart "$(nmap --script ssl-heartbleed -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap ssl-known-key"
expectpart "$(nmap --script ssl-known-key -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap ssl-poodle"
expectpart "$(nmap --script ssl-poodle -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap sslv2-drown"
expectpart "$(nmap --script sslv2-drown -p 443 127.0.0.1)" 0 "443/tcp open https"
new "nmap sslv2"
expectpart "$(nmap --script sslv2 -p 443 127.0.0.1)" 0 "443/tcp open https"
new "restconf get. Just ensure restconf is alive"
expectpart "$(curl $CURLOPTS -X GET $RCPROTO://127.0.0.1/.well-known/host-meta)" 0 'HTTP/1.1 200 OK' "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>" "<Link rel='restconf' href='/restconf'/>" "</XRD>"
if [ $RC -ne 0 ]; then
new "Kill restconf daemon"
@ -192,8 +207,8 @@ fi
unset RCPROTO
# Set by restconf_config
unset result
unset RESTCONFIG
unset RESTCONFIG1
rm -rf $dir