clixon/apps/restconf/restconf_native.c
Olof hagsand 12dddfd794 * RESTCONF,
* Refactored http1 code
  * fixed some http1/http2 only compile issues
2022-02-14 21:03:23 +01:00

898 lines
25 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <assert.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/resource.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif
/* cligen */
#include <cligen/cligen.h>
/* libclixon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_handle.h"
#include "restconf_err.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#include "restconf_nghttp2.h" /* http/2 */
#endif
#ifdef HAVE_HTTP1
#include "restconf_http1.h"
#endif
/*!
* @param[in] rc Restconf connection handle
* @see restconf_stream_free
*/
restconf_stream_data *
restconf_stream_data_new(restconf_conn *rc,
int32_t stream_id)
{
restconf_stream_data *sd;
if ((sd = malloc(sizeof(restconf_stream_data))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return NULL;
}
memset(sd, 0, sizeof(restconf_stream_data));
sd->sd_stream_id = stream_id;
sd->sd_fd = -1;
if ((sd->sd_inbuf = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
return NULL;
}
if ((sd->sd_indata = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
return NULL;
}
if ((sd->sd_outp_hdrs = cvec_new(0)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
return NULL;
}
if ((sd->sd_outp_buf = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
return NULL;
}
sd->sd_conn = rc;
INSQ(sd, rc->rc_streams);
return sd;
}
/*!
* @param[in] rc Restconf connection handle
*/
restconf_stream_data *
restconf_stream_find(restconf_conn *rc,
int32_t id)
{
restconf_stream_data *sd;
if ((sd = rc->rc_streams) != NULL) {
do {
if (sd->sd_stream_id == id)
return sd;
sd = NEXTQ(restconf_stream_data *, sd);
} while (sd && sd != rc->rc_streams);
}
return NULL;
}
/*
* @param[in] sd Restconf data stream
*/
int
restconf_stream_free(restconf_stream_data *sd)
{
if (sd->sd_fd != -1) {
close(sd->sd_fd);
}
if (sd->sd_inbuf)
cbuf_free(sd->sd_inbuf);
if (sd->sd_indata)
cbuf_free(sd->sd_indata);
if (sd->sd_outp_hdrs)
cvec_free(sd->sd_outp_hdrs);
if (sd->sd_outp_buf)
cbuf_free(sd->sd_outp_buf);
if (sd->sd_body)
cbuf_free(sd->sd_body);
if (sd->sd_path)
free(sd->sd_path);
if (sd->sd_settings2)
free(sd->sd_settings2);
if (sd->sd_qvec)
cvec_free(sd->sd_qvec);
free(sd);
return 0;
}
/*! Create restconf connection struct
*/
restconf_conn *
restconf_conn_new(clicon_handle h,
int s)
{
restconf_conn *rc;
if ((rc = (restconf_conn*)malloc(sizeof(restconf_conn))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return NULL;
}
memset(rc, 0, sizeof(restconf_conn));
rc->rc_h = h;
rc->rc_s = s;
return rc;
}
/*! Free clixon/cbuf resources related to a connection
* @param[in] rc restconf connection
*/
int
restconf_conn_free(restconf_conn *rc)
{
restconf_stream_data *sd;
if (rc == NULL){
clicon_err(OE_RESTCONF, EINVAL, "rc is NULL");
return -1;
}
#ifdef HAVE_LIBNGHTTP2
if (rc->rc_ngsession)
nghttp2_session_del(rc->rc_ngsession);
#endif
/* Free all streams */
while ((sd = rc->rc_streams) != NULL) {
DELQ(sd, rc->rc_streams, restconf_stream_data *);
if (sd)
restconf_stream_free(sd);
}
free(rc);
return 0;
}
/*! Given SSL connection, get peer certificate one-line name
* @param[in] ssl SSL session
* @param[out] oneline Cert name one-line
*/
int
ssl_x509_name_oneline(SSL *ssl,
char **oneline)
{
int retval = -1;
char *p = NULL;
X509 *cert = NULL;
X509_NAME *name;
if (ssl == NULL || oneline == NULL) {
clicon_err(OE_RESTCONF, EINVAL, "ssl or cn is NULL");
goto done;
}
if ((cert = SSL_get_peer_certificate(ssl)) == NULL)
goto ok;
if ((name = X509_get_subject_name(cert)) == NULL)
goto ok;
if ((p = X509_NAME_oneline(name, NULL, 0)) == NULL)
goto ok;
if ((*oneline = strdup(p)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
ok:
retval = 0;
done:
if (p)
OPENSSL_free(p);
if (cert)
X509_free(cert);
return retval;
}
/*! Check common connection sanity checks and terminate if found before request processing
*
* Tests of sanity of connection not really of an individual request, but is triggered by
* the (first) request in http/1 and http/2
* These tests maybe could have done earlier, this is somewhat late since the session is
* closed and that is always good to do as early as possible.
* The following are current checks:
* 1) Check if http/2 non-tls is disabled
* 2) Check if ssl client certs ae valid
* @param[in] h Clixon handle
* @param[in] rc Restconf connection handle
* @param[in] sd Http stream
* @param[out] term Terminate session
* @retval -1 Error
* @retval 0 OK
*/
int
restconf_connection_sanity(clicon_handle h,
restconf_conn *rc,
restconf_stream_data *sd)
{
int retval = -1;
cxobj *xerr = NULL;
long code;
cbuf *cberr = NULL;
restconf_media media_out = YANG_DATA_JSON;
char *media_str = NULL;
/* 1) Check if http/2 non-tls is disabled */
if (rc->rc_ssl == NULL &&
rc->rc_proto == HTTP_2 &&
clicon_option_bool(h, "CLICON_RESTCONF_HTTP2_PLAIN") == 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Only HTTP/2 with TLS is enabled, plain http/2 is disabled") < 0)
goto done;
if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
media_out = YANG_DATA_JSON;
}
else if ((int)(media_out = restconf_media_str2int(media_str)) == -1){
if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON;
}
if (api_return_err0(h, sd, xerr, 1, media_out, 0) < 0)
goto done;
rc->rc_exit = 1;
}
/* 2) Check if ssl client cert is valid */
else if (rc->rc_ssl != NULL &&
(code = SSL_get_verify_result(rc->rc_ssl)) != 0){
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cberr, "HTTP cert verification failed, unknown ca: (code:%ld)", code);
if (netconf_invalid_value_xml(&xerr, "protocol", cbuf_get(cberr)) < 0)
goto done;
if ((media_str = restconf_param_get(h, "HTTP_ACCEPT")) == NULL){
media_out = YANG_DATA_JSON;
}
else if ((int)(media_out = restconf_media_str2int(media_str)) == -1){
if (strcmp(media_str, "*/*") == 0) /* catch-all */
media_out = YANG_DATA_JSON;
}
if (api_return_err0(sd->sd_conn->rc_h, sd, xerr, 1, media_out, 0) < 0)
goto done;
rc->rc_exit = 1;
}
retval = 0;
done:
if (cberr)
cbuf_free(cberr);
if (xerr)
xml_free(xerr);
return retval;
}
/* Write buf to socket
* see also this function in restcont_api_openssl.c
*/
static int
native_buf_write(char *buf,
size_t buflen,
int s,
SSL *ssl)
{
int retval = -1;
ssize_t len;
ssize_t totlen = 0;
int er;
/* Two problems with debugging buffers that this fixes:
* 1. they are not "strings" in the sense they are not NULL-terminated
* 2. they are often very long
*/
if (clicon_debug_get()) {
char *dbgstr = NULL;
size_t sz;
sz = buflen>256?256:buflen; /* Truncate to 256 */
if ((dbgstr = malloc(sz+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memcpy(dbgstr, buf, sz);
dbgstr[sz] = '\0';
clicon_debug(1, "%s buflen:%zu buf:\n%s", __FUNCTION__, buflen, dbgstr);
free(dbgstr);
}
while (totlen < buflen){
if (ssl){
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
er = errno;
switch (SSL_get_error(ssl, len)){
case SSL_ERROR_SYSCALL: /* 5 */
if (er == ECONNRESET) {/* Connection reset by peer */
if (ssl)
SSL_free(ssl);
close(s);
clixon_event_unreg_fd(s, restconf_connection);
goto ok; /* Close socket and ssl */
}
else if (er == EAGAIN){
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
usleep(10000);
continue;
}
else{
clicon_err(OE_RESTCONF, er, "SSL_write %d", er);
goto done;
}
break;
default:
clicon_err(OE_SSL, 0, "SSL_write");
goto done;
break;
}
goto done;
}
}
else{
if ((len = write(s, buf+totlen, buflen-totlen)) < 0){
switch (errno){
case EAGAIN: /* Operation would block */
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
usleep(10000);
continue;
break;
case ECONNRESET: /* Connection reset by peer */
case EPIPE: /* Broken pipe */
close(s);
clixon_event_unreg_fd(s, restconf_connection);
goto ok; /* Close socket and ssl */
break;
default:
clicon_err(OE_UNIX, errno, "write %d", errno);
goto done;
break;
}
}
assert(len != 0);
}
totlen += len;
} /* while */
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Send early handcoded bad request reply before actual packet received, just after accept
* @param[in] h Clixon handle
* @param[in] s Socket
* @param[in] ssl If set, it will be freed
* @param[in] body If given add message body using media
* @see restconf_badrequest which can only be called in a request context
*/
int
native_send_badrequest(clicon_handle h,
int s,
SSL *ssl,
char *media,
char *body)
{
int retval = -1;
cbuf *cb = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n");
if (body){
cprintf(cb, "Content-Type: %s\r\n", media);
cprintf(cb, "Content-Length: %zu\r\n", strlen(body)+2); /* for \r\n */
}
else
cprintf(cb, "Content-Length: 0\r\n");
cprintf(cb, "\r\n");
if (body)
cprintf(cb, "%s\r\n", body);
if (native_buf_write(cbuf_get(cb), cbuf_len(cb), s, ssl) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
#ifdef HAVE_HTTP1
/*! Clear all input stream data if input is interrupted for some reason
*
* Only used by HTTP/1.
* @param[in] h Clixon handle
* @param[in] sd Http stream
* @retval 0 OK
* @retval -1 Error
*/
static int
native_clear_input(clicon_handle h,
restconf_stream_data *sd)
{
int retval = -1;
cbuf_reset(sd->sd_indata);
if (sd->sd_qvec)
cvec_free(sd->sd_qvec);
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
#endif
/*! Read HTTP from SSL socket
*
* @param[in] rc Restconf connection handle
* @param[in] buf Input buffer
* @param[in] sz Size of input buffer
* @param[out] np Bytes read
* @param[out] again If set, read data again, do not continue processing
* @retval -1 Error
* @retval 0 OK
*/
static int
read_ssl(restconf_conn *rc,
char *buf,
size_t sz,
ssize_t *np,
int *again)
{
int retval = -1;
int sslerr;
if ((*np = SSL_read(rc->rc_ssl, buf, sz)) < 0){
sslerr = SSL_get_error(rc->rc_ssl, *np);
clicon_debug(1, "%s SSL_read() n:%zd errno:%d sslerr:%d", __FUNCTION__, *np, errno, sslerr);
switch (sslerr){
case SSL_ERROR_WANT_READ: /* 2 */
/* SSL_ERROR_WANT_READ is returned when the last operation was a read operation
* from a nonblocking BIO.
* That is, it can happen if restconf_socket_init() below is called
* with SOCK_NONBLOCK
*/
clicon_debug(1, "%s SSL_read SSL_ERROR_WANT_READ", __FUNCTION__);
usleep(1000);
*again = 1;
break;
default:
clicon_err(OE_XML, errno, "SSL_read");
goto done;
} /* switch */
}
retval = 0;
done:
return retval;
}
/*! Read HTTP from regular socket
*
* @param[in] rc Restconf connection handle
* @param[in] buf Input buffer
* @param[in] sz Size of input buffer
* @param[out] np Bytes read
* @param[out] again If set, read data again, do not continue processing
* @retval -1 Error
* @retval 0 Socket closed, quit
* @retval 1 OK
* XXX:
* readmore/continue
* goto ok
*/
static int
read_regular(restconf_conn *rc,
char *buf,
size_t sz,
ssize_t *np,
int *again)
{
int retval = -1;
if ((*np = read(rc->rc_s, buf, sz)) < 0){ /* XXX atomicio ? */
switch(errno){
case ECONNRESET:/* Connection reset by peer */
clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s);
clixon_event_unreg_fd(rc->rc_s, restconf_connection);
close(rc->rc_s);
restconf_conn_free(rc);
retval = 0; /* Close socket and ssl */
goto done;
break;
case EAGAIN:
clicon_debug(1, "%s read EAGAIN", __FUNCTION__);
usleep(1000);
*again = 1;
break;
default:;
clicon_err(OE_XML, errno, "read");
goto done;
break;
}
}
retval = 1;
done:
return retval;
}
#ifdef HAVE_HTTP1
/*! RESTCONF HTTP/1 processing after chunk of bytes read
*
* @param[in] rc Restconf connection handle
* @param[in] buf Input buffer
* @param[in] n Length of data in input buffer
* @param[out] readmore If set, read data again, do not continue processing
* @retval -1 Error
* @retval 0 Socket closed, quit
* @retval 1 OK
*/
static int
restconf_http1(restconf_conn *rc,
char *buf,
size_t n,
int *readmore)
{
int retval = -1;
restconf_stream_data *sd;
clicon_handle h;
int ret;
int status;
h = rc->rc_h;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
goto done;
}
/* Two states for reading:
* 1) Initial reading of headers, parse from start
* 2) Headers are read / body started, dont parse, just append body
*/
/* Check whole message is read.
* Only way this could happen is that body is read
* 0: No Content-Length or 0
* header does not contain Content-Length or is 0
* (OR: message header not fully read SHOULDNT HAPPEN IF BODY READ)
* 1: Content-Length found but body has fewer bytes, ie remaining bytes to read
* 2: Content-Length found and matches body length. No more bytes to read
*/
if ((ret = http1_check_content_length(h, sd, &status)) < 0)
goto done;
if (status == 1){ /* Next read: keep header state and only append inbody */
if (cbuf_append_buf(sd->sd_indata, buf, n) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append");
goto done;
}
}
else {
/* multi-buffer for multiple reads
* This is different from sd_indata that it is before and includes headers
*/
if (cbuf_append_buf(sd->sd_inbuf, buf, n) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append");
goto done;
}
if (clixon_http1_parse_string(h, rc, cbuf_get(sd->sd_inbuf)) < 0){
/* XXX This does not work for SSL */
if (rc->rc_ssl){
ret = SSL_pending(rc->rc_ssl);
}
else if ((ret = clixon_event_poll(rc->rc_s)) < 0)
goto done;
if (ret > 0){
if (native_clear_input(h, sd) < 0)
goto done;
(*readmore)++;
goto ok;
}
/* Return error. Honsetly, the sender could just e slow, it should really be a
* timeout here.
*/
if (native_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;
}
/* Check for Continue and if so reply with 100 Continue
* ret == 1: send reply
*/
if ((ret = http1_check_expect(h, rc, sd)) < 0)
goto done;
if (ret == 1){
if (native_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);
cbuf_reset(sd->sd_outp_buf);
}
}
/* Check whole message is read.
* Only way this could happen is that body is read
* 0: No Content-Length or 0
* header does not contain Content-Length or is 0
* (OR: message header not fully read SHOULDNT HAPPEN IF BODY READ OK in parse above)
* 1: Content-Length found but body has fewer bytes, ie remaining bytes to read
* 2: Content-Length found and matches body length. No more bytes to read
*/
if ((ret = http1_check_content_length(h, sd, &status)) < 0)
goto done;
if (status == 1){
(*readmore)++;
goto ok;
}
/* main restconf processing */
if (restconf_http1_path_root(h, rc) < 0)
goto done;
if (native_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 */
SSL_free(rc->rc_ssl);
rc->rc_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);
restconf_conn_free(rc);
retval = 0;
goto done;
}
ok:
retval = 1;
done:
return retval;
}
#endif
static int
restconf_http2_upgrade(restconf_conn *rc)
{
int retval = -1;
restconf_stream_data *sd;
if ((sd = restconf_stream_find(rc, 0)) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "restconf stream not found");
goto done;
}
if (sd->sd_upgrade2){
nghttp2_error ngerr;
/* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */
rc->rc_proto = HTTP_2;
if (http2_session_init(rc) < 0){
restconf_close_ssl_socket(rc, 1);
goto done;
}
/* The HTTP/1.1 request that is sent prior to upgrade is assigned a
* stream identifier of 1 (see Section 5.1.1) with default priority
*/
sd->sd_stream_id = 1;
/* The first HTTP/2 frame sent by the server MUST be a server connection
* preface (Section 3.5) consisting of a SETTINGS frame (Section 6.5).
*/
if ((ngerr = nghttp2_session_upgrade2(rc->rc_ngsession,
sd->sd_settings2,
sd->sd_settings2?strlen((const char*)sd->sd_settings2):0,
0, /* XXX: 1 if HEAD */
NULL)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_upgrade2");
goto done;
}
if (http2_send_server_connection(rc) < 0){
restconf_close_ssl_socket(rc, 1);
goto done;
}
/* Use params from original http/1 session to http/2 stream */
if (http2_exec(rc, sd, rc->rc_ngsession, 1) < 0)
goto done;
/*
* Very special case for http/1->http/2 upgrade and restconf "restart"
* That is, the restconf daemon is restarted under the hood, and the session
* is closed in mid-step: it needs a couple of extra rounds to complete the http/2
* settings before it completes.
* Maybe a more precise way would be to encode that semantics using recieved http/2
* frames instead of just postponing nrof events?
*/
if (clixon_exit_get() == 1){
clixon_exit_set(3);
}
}
retval = 0;
done:
return retval;
}
/*!
* @param[in] buf Input buffer
* @param[in] n Size of input buffer
* @retval -1 Error
* @retval 0 Socket closed, quit
* @retval 1 OK
*/
static int
restconf_http2(restconf_conn *rc,
char *buf,
size_t n,
int *readmore)
{
int retval = -1;
int ret;
nghttp2_error ngerr;
if (rc->rc_exit){ /* Server-initiated exit for http/2 */
if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, 0)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr);
goto done; // XXX not here in original?
}
}
else {
if ((ret = http2_recv(rc, (unsigned char *)buf, n)) < 0)
goto done;
if (ret == 0){
restconf_close_ssl_socket(rc, 1);
if (restconf_conn_free(rc) < 0)
goto done;
retval = 0;
goto done;
}
/* There may be more data frames */
(*readmore)++;
}
retval = 1;
done:
return retval;
}
/*! 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).
* @retval 0 OK
* @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: returns a buffer
* with 100 Continue, in which case that is replied and the function returns and the client sends
* more data.
* OR returns 0 with no reply, then this is assumed to mean read more data from the socket.
*/
int
restconf_connection(int s,
void *arg)
{
int retval = -1;
restconf_conn *rc = NULL;
ssize_t n;
// char buf[1024]; /* Alter BUFSIZ (8K) from stdio.h 8K. 256 fails some tests */
char buf[32]; /* Alter BUFSIZ (8K) from stdio.h 8K. 256 fails some tests */
int readmore = 1;
int ret;
clicon_debug(1, "%s %d", __FUNCTION__, s);
if ((rc = (restconf_conn*)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
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 (rc->rc_ssl){
if (read_ssl(rc, buf, sizeof(buf), &n, &readmore) < 0)
goto done;
}
else{ /* Not SSL */
if ((ret = read_regular(rc, buf, sizeof(buf), &n, &readmore)) < 0)
goto done;
if (ret == 0)
goto ok; /* abort here */
}
clicon_debug(1, "%s read:%zd", __FUNCTION__, n);
if (readmore)
continue;
if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (restconf_close_ssl_socket(rc, 0) < 0)
goto done;
restconf_conn_free(rc);
rc = NULL;
goto ok;
}
switch (rc->rc_proto){
#ifdef HAVE_HTTP1
case HTTP_10:
case HTTP_11:
if ((ret = restconf_http1(rc, buf, n, &readmore)) < 0)
goto done;
if (ret == 0)
goto ok;
if (readmore)
continue;
#ifdef HAVE_LIBNGHTTP2
if (restconf_http2_upgrade(rc) < 0)
goto done;
#endif /* HAVE_LIBNGHTTP2 */
break;
#endif /* HAVE_HTTP1 */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:
if ((ret = restconf_http2(rc, buf, n, &readmore)) < 0)
goto done;
if (ret == 0)
goto ok;
if (readmore)
continue;
break;
#endif /* HAVE_LIBNGHTTP2 */
default:
break;
} /* switch rc_proto */
} /* while readmore */
ok:
retval = 0;
done:
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
return retval;
} /* restconf_connection */