* HTTP/1 native parser as part of the RESTCONF client

* Fixed memory error in opendir/readdir in clicon_file_dirent
* Remove MAXPATH in parsers
* New string-del function
This commit is contained in:
Olof hagsand 2022-01-26 13:48:20 +01:00
parent 0ed34b4fab
commit dadf4a778a
53 changed files with 1061 additions and 1273 deletions

View file

@ -49,18 +49,6 @@
* +--------------------+
* Note restconf_request may need to be extended eg backpointer to rs?
*
* +--------------------+ +--------------------+
* | evhtp_connection | -----> | evhtp_t (core) |
* +--------------------+ +--------------------+
* |request ^
* v | conn
* +--------------------+ +--------------------+
* | evhtp_request | --> uri | evhtp_uri |
* +--------------------+ +--------------------+
* | (created by parser) | | |
* v v v v
* headers/buffers/method/... authority path query
*
* Buffer handling:
* c
* |
@ -121,12 +109,6 @@
#include "clixon_config.h" /* generated by config & autoconf */
#endif
/* The clixon evhtp code can be compiled with or without threading support
* The choice is set at libevhtp compile time by cmake. Eg:
* cmake -DEVHTP_DISABLE_EVTHR=ON # Disable threads.
* Default in testing is disabled threads.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
@ -151,16 +133,6 @@
/* clicon */
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
/* nghttp2 */
#include <nghttp2/nghttp2.h>
@ -173,12 +145,12 @@
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBEVHTP
#include "restconf_evhtp.h" /* http/1 */
#endif
#ifdef HAVE_LIBNGHTTP2
#include "restconf_nghttp2.h" /* http/2 */
#endif
#ifdef HAVE_HTTP1
#include "clixon_http1.h"
#endif
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:E:l:p:y:a:u:rW:R:o:"
@ -352,9 +324,7 @@ clixon_openssl_log_cb(void *handle,
return 0;
}
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4757
/*! Init openSSL
*/
static int
init_openssl(void)
@ -425,7 +395,7 @@ restconf_verify_certs(int preverify_ok,
* - 0 (preferity_ok) the session terminates here in SSL negotiation, an http client
* will get a low level error (not http reply)
* - 1 Check if the cert is valid using SSL_get_verify_result(rc->rc_ssl)
* @see restconf_evhtp_sanity and restconf_nghttp2_sanity where this is done for http/1 and http/2
* @see restconf_nghttp2_sanity where this is done for http/1 and http/2
*/
return 1;
}
@ -479,7 +449,7 @@ alpn_select_proto_cb(SSL *ssl,
inp++;
if (clicon_debug_get()) /* debug print the protoocol */
alpn_proto_dump(__FUNCTION__, (const char*)inp, len);
#ifdef HAVE_LIBEVHTP
#ifdef HAVE_HTTP1
if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){
*outlen = len;
*out = inp;
@ -503,8 +473,6 @@ alpn_select_proto_cb(SSL *ssl,
}
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4794
*/
static SSL_CTX *
restconf_ssl_context_create(clicon_handle h)
@ -535,8 +503,6 @@ restconf_ssl_context_create(clicon_handle h)
}
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init: 4886
* @param[in] ctx SSL context
* @param[in] server_cert_path Server cert
* @param[in] server_key_path Server private key
@ -595,7 +561,7 @@ restconf_ssl_context_configure(clixon_handle h,
return retval;
}
/*! Utility function to close restconf server ssl/evhtp socket.
/*! Utility function to close restconf server ssl socket.
* There are many variants to closing, one could probably make this more generic
* and always use this function, but it is difficult.
*/
@ -618,10 +584,6 @@ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that
}
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
#ifdef HAVE_LIBEVHTP
if (rc->rc_evconn)
rc->rc_evconn->ssl = NULL;
#endif
}
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
@ -675,7 +637,67 @@ send_badrequest(clicon_handle h,
return retval;
}
/*! New data connection after accept, receive and reply on data sockte
#if 0
#define IFILE "/var/tmp/clixon-mirror/ifile"
#define FMTDIR "/var/tmp/clixon-mirror/"
static FILE *myf = NULL;
static int
mirror_pkt(const char *buf,
ssize_t n)
{
int retval = -1;
if (fwrite(buf, 1, n, myf) != n){
perror("fopen");
goto done;
}
retval = 0;
done:
return retval;
}
static int
mirror_new(void)
{
int retval = -1;
static uint64_t u64 = 0;
cbuf *cb = cbuf_new();
FILE *ifile;
if ((ifile = fopen(IFILE, "r+")) == NULL){
perror("fopen r+ ifile");
}
else {
if (fscanf(ifile, "%" PRIu64, &u64) < 0){
perror("fscanf ifile");
goto done;
}
fclose(ifile);
}
if (myf != NULL)
fclose(myf);
cprintf(cb, FMTDIR "%" PRIu64 ".dump", u64);
if ((myf = fopen(cbuf_get(cb), "w")) == NULL){
perror("fopen");
goto done;
}
cbuf_free(cb);
u64++;
if ((ifile = fopen(IFILE, "w")) == NULL){
perror("fopen w+ ifile");
goto done;
}
fprintf(ifile, "%" PRIu64, u64);
fclose(ifile);
retval = 0;
done:
return retval;
}
#endif
/*! New data connection after accept, receive and reply on data socket
*
* @param[in] s Socket where message arrived. read from this.
* @param[in] arg Client entry (from).
@ -683,10 +705,10 @@ send_badrequest(clicon_handle h,
* @retval -1 Error Terminates backend and is never called). Instead errors are
* propagated back to client.
* @see restconf_accept_client where this callback is registered
* @note read buffer is limited. More data can be read in two ways: either evhtp returns a buffer
* @note read buffer is limited. More data can be read in two ways: returns a buffer
* with 100 Continue, in which case that is replied and the function returns and the client sends
* more data.
* OR evhtp returns 0 with no reply, then this is assumed to mean read more data from the socket.
* OR returns 0 with no reply, then this is assumed to mean read more data from the socket.
*/
static int
restconf_connection(int s,
@ -701,9 +723,8 @@ restconf_connection(int s,
#ifdef HAVE_LIBNGHTTP2
int ret;
#endif
#ifdef HAVE_LIBEVHTP
#ifdef HAVE_HTTP1
clicon_handle h;
evhtp_connection_t *evconn = NULL;
restconf_stream_data *sd;
#endif
@ -765,7 +786,7 @@ restconf_connection(int s,
continue;
}
}
clicon_debug(1, "%s (ssl)read:%zd", __FUNCTION__, n);
clicon_debug(1, "%s read:%zd", __FUNCTION__, n);
if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (restconf_close_ssl_socket(rc, 0) < 0)
@ -774,99 +795,43 @@ restconf_connection(int s,
rc = NULL;
goto ok;
}
#if 0
if (mirror_pkt(buf, n) < 0)
goto done;
#endif
switch (rc->rc_proto){
#ifdef HAVE_LIBEVHTP
#ifdef HAVE_HTTP1
case HTTP_10:
case HTTP_11:
h = rc->rc_h;
/* parse incoming packet using evhtp
* signature:
*/
evconn = rc->rc_evconn;
/* This is the main call to EVHTP parser */
if (connection_parse_nobev(buf, n, evconn) < 0){
clicon_debug(1, "%s connection_parse error", __FUNCTION__);
/* XXX To get more nuanced evhtp error check
* htparser_get_error(conn->parser)
*/
if (clixon_http1_parse_buf(h, rc, buf, n) < 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>The requested URL or a header is in some way badly formed</error-message></error></errors>") < 0)
goto done;
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
evconn->ssl = NULL;
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
}
else{
if (restconf_http1_path_root(h, rc) < 0)
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn);
restconf_conn_free(rc);
goto ok;
} /* connection_parse_nobev */
}
clicon_debug(1, "%s connection_parse OK", __FUNCTION__);
/* default stream */
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
goto done;
}
if (evconn->bev != NULL){
struct evbuffer *ev;
size_t buflen0;
size_t buflen1;
char *buf = NULL;
if ((ev = bufferevent_get_output(evconn->bev)) != NULL){
buflen0 = evbuffer_get_length(ev);
buflen1 = buflen0 - rc->rc_bufferevent_output_offset;
if (buflen1 > 0){
buf = (char*)evbuffer_pullup(ev, -1);
/* XXX Here if -1 in api_root
* HTTP/1.1 0 UNKNOWN\r\nContent-Length: 0
* And output_buffer is NULL
*/
/* If evhtp has print an output buffer, clixon whould not have done it
* Shouldnt happen
*/
if (cbuf_len(sd->sd_outp_buf)){
clicon_debug(1, "%s Warning: evhtp printed output buffer, but clixon output buffer is non-empty %s",
__FUNCTION__, cbuf_get(sd->sd_outp_buf));
cbuf_reset(sd->sd_outp_buf);
}
if (cbuf_append_buf(sd->sd_outp_buf, buf, buflen1) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append_buf");
goto done;
}
/* XXX Cant get drain to work, need to keep an offset */
evbuffer_drain(ev, -1);
rc->rc_bufferevent_output_offset += buflen1;
}
}
if (cbuf_len(sd->sd_outp_buf) == 0)
readmore = 1;
else {
if (buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
rc->rc_s, rc->rc_ssl) < 0)
goto done;
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(sd->sd_outp_buf);
}
}
else{
if (send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>No evhtp output</error-message></error></errors>") < 0)
goto done;
}
if (buf_write(cbuf_get(sd->sd_outp_buf), cbuf_len(sd->sd_outp_buf),
rc->rc_s, rc->rc_ssl) < 0)
goto done;
cvec_reset(sd->sd_outp_hdrs); /* Can be done in native_send_reply */
cbuf_reset(sd->sd_outp_buf);
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
SSL_free(rc->rc_ssl);
rc->rc_ssl = NULL;
evconn->ssl = NULL;
if (close(rc->rc_s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
clicon_debug(1, "%s evconn-free (%p) 2", __FUNCTION__, evconn);
restconf_conn_free(rc);
goto ok;
}
@ -916,7 +881,7 @@ restconf_connection(int s,
}
#endif
break;
#endif /* HAVE_LIBEVHTP */
#endif /* HAVE_HTTP1 */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
@ -1119,10 +1084,9 @@ restconf_accept_client(int fd,
clicon_debug(1, "%s %d", __FUNCTION__, fd);
#ifdef HAVE_LIBNGHTTP2
#ifndef HAVE_LIBEVHTP
#ifndef HAVE_HTTP1
proto = HTTP_2; /* If nghttp2 only let default be 2.0 */
#endif
/* If also evhtp, keep HTTP_11 */
#endif
if ((rsock = (restconf_socket *)arg) == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
@ -1313,28 +1277,14 @@ restconf_accept_client(int fd,
} /* if ssl */
rc->rc_proto = proto;
switch (rc->rc_proto){
#ifdef HAVE_LIBEVHTP
#ifdef HAVE_HTTP1
case HTTP_10:
case HTTP_11:{
evhtp_t *evhtp = (evhtp_t *)rh->rh_arg;
evhtp_connection_t *evconn;
/* Create evhtp-specific struct */
if ((evconn = evhtp_connection_new_server(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_evconn = evconn; /* Generic to specific */
evconn->arg = rc; /* Specific to generic */
evconn->ssl = rc->rc_ssl; /* evhtp */
case HTTP_11:
/* Create a default stream for http/1 */
if (restconf_stream_data_new(rc, 0) == NULL)
goto done;
}
break;
#endif /* HAVE_LIBEVHTP */
#endif /* HAVE_HTTP1 */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:{
if (http2_session_init(rc) < 0){
@ -1365,6 +1315,10 @@ restconf_accept_client(int fd,
default:
break;
} /* switch proto */
#if 0
if (mirror_new() < 0)
goto done;
#endif
if (clixon_event_reg_fd(rc->rc_s, restconf_connection, (void*)rc, "restconf client socket") < 0)
goto done;
ok:
@ -1376,6 +1330,8 @@ restconf_accept_client(int fd,
return retval;
} /* restconf_accept_client */
/*!
*/
static int
restconf_native_terminate(clicon_handle h)
{
@ -1396,18 +1352,6 @@ restconf_native_terminate(clicon_handle h)
}
if (rh->rh_ctx)
SSL_CTX_free(rh->rh_ctx);
#ifdef HAVE_LIBEVHTP
{
evhtp_t *evhtp = (evhtp_t *)rh->rh_arg;
if (evhtp){
if (evhtp->evbase)
event_base_free(evhtp->evbase);
evhtp_free(evhtp);
rh->rh_arg = NULL;
}
}
#endif /* HAVE_LIBEVHTP */
free(rh);
}
EVP_cleanup();
@ -1581,10 +1525,6 @@ 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 */
@ -1639,28 +1579,6 @@ 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");
goto done;
}
/* This is socket create a new evhtp_t instance */
if ((evhtp = evhtp_new((void*)evbase, h)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
rh->rh_arg = evhtp;
if (evhtp_set_cb(evhtp, "/" RESTCONF_API, restconf_path_root, h) == NULL){
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
}
/* Callback to be executed for all /restconf api calls */
if (evhtp_set_cb(evhtp, RESTCONF_WELL_KNOWN, restconf_path_wellknown, h) == NULL){
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;