From 0ad577fa81fe1d5fb9360cfe1c5a727535c4a591 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 31 May 2021 19:03:19 +0200 Subject: [PATCH] - 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 // --- CHANGELOG.md | 2 + apps/restconf/restconf_api_native.c | 20 +- apps/restconf/restconf_lib.c | 20 ++ apps/restconf/restconf_lib.h | 10 + apps/restconf/restconf_main_native.c | 482 +++++++++++++++++---------- apps/restconf/restconf_methods.c | 5 +- apps/restconf/restconf_native.h | 21 +- configure | 166 +++++++-- configure.ac | 45 ++- include/clixon_config.h.in | 8 +- lib/src/clixon_path.c | 8 + lib/src/clixon_xml.c | 4 + test/test_restconf.sh | 5 +- test/test_restconf_nmap.sh | 129 +++---- 14 files changed, 634 insertions(+), 291 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b572922e..85333298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 76d10c2c..80bdd61b 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -49,11 +49,13 @@ #include #include +#ifdef HAVE_LIBEVHTP /* evhtp */ #define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_EVTHR #include +#endif /* HAVE_LIBEVHTP */ /* cligen */ #include @@ -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 */ } diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 40e237f6..b0f545a2 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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: diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 89bef5c6..c2a3357f 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -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); diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index 49f2d05c..406a6a4b 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -151,6 +151,7 @@ /* clicon */ #include +#ifdef HAVE_LIBEVHTP /* evhtp */ #include /* evbuffer */ #define EVHTP_DISABLE_REGEX @@ -158,6 +159,12 @@ #include #include /* XXX inline this / use SSL directly */ +#endif /* HAVE_LIBEVHTP */ + +#ifdef HAVE_LIBNGHTTP2 +/* nghttp2 */ +#include +#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", "protocolmalformed-messageThe requested URL or a header is in some way badly formed") < 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", "protocolmalformed-messageNo evhtp output") < 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, "protocolmalformed-messageALPN: protocol not recognized: %s", 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", "protocolmalformed-messageThe plain HTTP request was sent to HTTPS port") < 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", "protocolmalformed-messagePeer certificate required") < 0) + if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", + "protocolmalformed-messagePeer certificate required") < 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", "protocolmalformed-messagePeer certificate required") < 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; diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 0bfdf689..26664316 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -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; diff --git a/apps/restconf/restconf_native.h b/apps/restconf/restconf_native.h index de4002fe..54fd61d2 100644 --- a/apps/restconf/restconf_native.h +++ b/apps/restconf/restconf_native.h @@ -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; /* diff --git a/configure b/configure index c626d1c0..437d5ea0 100755 --- a/configure +++ b/configure @@ -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 diff --git a/configure.ac b/configure.ac index 91550c47..7481601c 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 55a22ab3..ae1ae28a 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -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 header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the 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 diff --git a/lib/src/clixon_path.c b/lib/src/clixon_path.c index e3ec5ee3..38d62300 100644 --- a/lib/src/clixon_path.c +++ b/lib/src/clixon_path.c @@ -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) diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index f019c1a5..750bbab2 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -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) diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 9e59a8af..5da8f40f 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -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 diff --git a/test/test_restconf_nmap.sh b/test/test_restconf_nmap.sh index 9337fa23..f0537f3b 100755 --- a/test/test_restconf_nmap.sh +++ b/test/test_restconf_nmap.sh @@ -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 < +# Create a single IPv4 https socket +RESTCONFIG=$(cat < true none $srvcert $srvkey $cakey false - default
0.0.0.0
80false
- default
0.0.0.0
443true
- default
::
80false
- default
::
443true
- -EOF -) -else - # For backend config, create 2 sockets, all combinations IPv4 + http/https - RESTCONFIG1=$(cat < - true - none - $srvcert - $srvkey - $cakey - false - default
0.0.0.0
80false
default
0.0.0.0
443true
EOF ) -fi + # Clixon config cat < $cfg @@ -124,8 +95,8 @@ cat < $cfg /usr/local/var/$APPNAME/$APPNAME.pidfile /usr/local/var/$APPNAME true - $USEBACKEND - $RESTCONFIG + false + $RESTCONFIG EOF @@ -145,11 +116,11 @@ fi new "wait backend" wait_backend -new "netconf edit config" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$RESTCONFIG1]]>]]>" "^]]>]]>$" +#new "netconf edit config" +#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$RESTCONFIG]]>]]>" "^]]>]]>$" -new "netconf commit" -expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" +#new "netconf commit" +#expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" 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 " - 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' "" "" "" 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