/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* libssl 1.1 API
* Data structures:
* 1 1
* +--------------------+ restconf_handle_get +--------------------+
* | rh restconf_handle | <--------------------- | h clicon_handle |
* +--------------------+ +--------------------+
* common SSL config \ ^
* \ | n
* \ rh_sockets +--------------------+
* +-----------> | rs restconf_socket |
* +--------------------+
* n per-socket SSL config
* +--------------------+
* | rr restconf_request| per-packet
* +--------------------+
* 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
* |
* +--------------------+
* | bufferevent |
* +--------------------+
* | |
* input output
*
* Three special cases and expected (typical replies):
*
* Note (1) http to https port:
* olof@alarik> curl -Ssik -X GET http://www.hagsand.se:443
* HTTP/1.1 400 Bad Request
* Server: nginx
* Date: Tue, 30 Mar 2021 06:46:34 GMT
* Content-Type: text/html
* Content-Length: 248
* Connection: close
*
*
*
400 The plain HTTP request was sent to HTTPS port
*
*
400 Bad Request
*
The plain HTTP request was sent to HTTPS port
*
nginx
*
*
*
* Note (2) https to http port:
* olof@alarik> curl -Ssik -X GET https://www.hagsand.se:80
* curl: (35) error:1408F10B:SSL routines:ssl3_get_record:wrong version number
*
* Note (3) client-cert is NULL:
* curl -i -m 40 -k -H 'Content-Type: application/yang-data+json' https://10.255.10.1/restconf/data/netgate-system:system/auth
* HTTP/2 400
* [1mserver[0m: nginx/1.16.1
* [1mdate[0m: Sun, 28 Feb 2021 17:34:08 GMT
* [1mcontent-type[0m: text/html
* [1mcontent-length[0m: 237
*
* 400 No required SSL certificate was sent
*
*
400 Bad Request
*
No required SSL certificate was sent
*
nginx/1.16.1
*
*
*
* Example links regarding certificate verification
* https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html
* http://fm4dd.com/openssl/certverify.htm
* https://wiki.openssl.org/index.php/Simple_TLS_Server
* https://wiki.openssl.org/index.php/Hostname_validation
*/
#ifdef HAVE_CONFIG_H
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* cligen */
#include
/* clicon */
#include
/* evhtp */
#include /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include
#include /* XXX inline this / use SSL directly */
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_handle.h"
#include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:E:l:p:y:a:u:ro:"
/* If set, open outwards socket non-blocking, as opposed to blocking
* Should work both ways, but in the ninblocking case,
* the read/write/accept calls have an iteration to read/write/accept again in case
* they are not completed in time. Note, in the accept case it has to do with SSL handshake.
* Currently, the end-result is the same in both cases.
* But it gets important in a multi-threaded setup.
*/
#define RESTCONF_OPENSSL_NONBLOCKING 1
/* See see listen(5) */
#define SOCKET_LISTEN_BACKLOG 8
/* Cert verify depth: dont know what to set here? */
#define VERIFY_DEPTH 5
/* Forward */
static int restconf_connection(int s, void* arg);
static int session_id_context = 1;
/*! Get restconf native handle
* @param[in] h Clicon handle
* @retval rh Restconf native handle
*/
static restconf_native_handle *
restconf_native_handle_get(clicon_handle h)
{
clicon_hash_t *cdat = clicon_data(h);
size_t len;
void *p;
if ((p = clicon_hash_value(cdat, "restconf-native-handle", &len)) != NULL)
return *(restconf_native_handle **)p;
return NULL;
}
/*! Set restconf native handle
* @param[in] h Clicon handle
* @param[in] rh Restconf native handle (malloced pointer)
*/
static int
restconf_native_handle_set(clicon_handle h,
restconf_native_handle *rh)
{
clicon_hash_t *cdat = clicon_data(h);
/* It is the pointer to ys that should be copied by hash,
so we send a ptr to the ptr to indicate what to copy.
*/
if (clicon_hash_add(cdat, "restconf-native-handle", &rh, sizeof(rh)) == NULL)
return -1;
return 0;
}
/* Write evbuf to socket
* see also this function in restcont_api_openssl.c
*/
static int
buf_write(char *buf,
size_t buflen,
int s,
SSL *ssl)
{
int retval = -1;
ssize_t len;
ssize_t totlen = 0;
int er;
clicon_debug(1, "%s buflen:%lu buf:%s", __FUNCTION__, buflen, buf);
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){
if (errno == EAGAIN){
clicon_debug(1, "%s write EAGAIN", __FUNCTION__);
usleep(10000);
continue;
}
#if 1
else if (errno == ECONNRESET) {/* Connection reset by peer */
close(s);
clixon_event_unreg_fd(s, restconf_connection);
goto ok; /* Close socket and ssl */
}
#endif
else{
clicon_err(OE_UNIX, errno, "write");
goto done;
}
}
assert(len != 0);
}
totlen += len;
} /* while */
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/* util function to append log string
*/
static int
print_cb(const char *str, size_t len, void *cb)
{
return cbuf_append_str((cbuf*)cb, (char*)str); /* Assume string */
}
/* Clixon error category log callback
* @param[in] handle Application-specific handle
* @param[out] cb Read log/error string into this buffer
*/
static int
openssl_cat_log_cb(void *handle,
cbuf *cb)
{
clicon_debug(1, "%s", __FUNCTION__);
ERR_print_errors_cb(print_cb, cb);
return 0;
}
static char*
evhtp_method2str(enum htp_method m)
{
switch (m){
case htp_method_GET:
return "GET";
break;
case htp_method_HEAD:
return "HEAD";
break;
case htp_method_POST:
return "POST";
break;
case htp_method_PUT:
return "PUT";
break;
case htp_method_DELETE:
return "DELETE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PATCH:
return "PATCH";
break;
#ifdef NOTUSED
case htp_method_MKCOL:
return "MKCOL";
break;
case htp_method_COPY:
return "COPY";
break;
case htp_method_MOVE:
return "MOVE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PROPFIND:
return "PROPFIND";
break;
case htp_method_PROPPATCH:
return "PROPPATCH";
break;
case htp_method_LOCK:
return "LOCK";
break;
case htp_method_UNLOCK:
return "UNLOCK";
break;
case htp_method_TRACE:
return "TRACE";
break;
case htp_method_CONNECT:
return "CONNECT";
break;
#endif /* NOTUSED */
default:
return "UNKNOWN";
break;
}
}
static int
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)
{
cvec *qvec = (cvec *)arg;
char *key;
char *val;
char *valu = NULL; /* unescaped value */
cg_var *cv;
key = hdr->key;
val = hdr->val;
if (uri_percent_decode(val, &valu) < 0)
return -1;
if ((cv = cvec_add(qvec, CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_add");
return -1;
}
cv_name_set(cv, key);
cv_string_set(cv, valu);
if (valu)
free(valu);
return 0;
}
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
static int
convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
cbuf *cb = NULL;
int i;
char c;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* convert key name */
cprintf(cb, "HTTP_");
for (i=0; ikey); i++){
c = hdr->key[i] & 0xff;
if (islower(c))
cprintf(cb, "%c", toupper(c));
else if (c == '-')
cprintf(cb, "_");
else
cprintf(cb, "%c", c);
}
if (restconf_param_set(h, cbuf_get(cb), hdr->val) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Map from evhtp information to "fcgi" type parameters used in clixon code
*
* While all these params come via one call in fcgi, the information must be taken from
* several different places in evhtp
* @param[in] h Clicon handle
* @param[in] req Evhtp request struct
* @param[out] qvec Query parameters, ie the ?=&= stuff
* @retval 1 OK continue
* @retval 0 Fail, dont continue
* @retval -1 Error
* The following parameters are set:
* QUERY_STRING
* REQUEST_METHOD
* REQUEST_URI
* HTTPS
* HTTP_HOST
* HTTP_ACCEPT
* HTTP_CONTENT_TYPE
* @note there may be more used by an application plugin
*/
static int
evhtp_params_set(clicon_handle h,
evhtp_request_t *req,
cvec *qvec)
{
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
evhtp_ssl_t *ssl = NULL;
char *subject = NULL;
cvec *cvv = NULL;
char *cn;
cxobj *xerr = NULL;
int pretty;
if ((uri = req->uri) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No uri");
goto done;
}
if ((path = uri->path) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No path");
goto done;
}
meth = evhtp_request_get_method(req);
/* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string?
* This is different from all else: Ie one could have re-created a string here but
* that would mean double parsing,...
*/
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
goto done;
}
if (restconf_param_set(h, "REQUEST_METHOD", evhtp_method2str(meth)) < 0)
goto done;
if (restconf_param_set(h, "REQUEST_URI", path->full) < 0)
goto done;
clicon_debug(1, "%s proto:%d", __FUNCTION__, req->proto);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* XXX: Any two http numbers seem accepted by evhtp, like 1.99, 99.3 as http/1.1*/
if (req->proto != EVHTP_PROTO_10 &&
req->proto != EVHTP_PROTO_11){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid HTTP version number") < 0)
goto done;
/* Select json as default since content-type header may not be accessible yet */
if (api_return_err0(h, req, xerr, pretty, YANG_DATA_JSON, 0) < 0)
goto done;
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
goto done;
}
}
}
/* 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)
goto done;
retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (subject)
free(subject);
if (xerr)
xml_free(xerr);
if (cvv)
cvec_free(cvv);
return retval;
fail:
retval = 0;
goto done;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
* Discussion: problematic if fatal error -1 is returneod from clixon routines
* without actually terminating. Consider:
* 1) sending some error? and/or
* 2) terminating the process?
*/
static void
restconf_path_root(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
evhtp_connection_t *conn;
int ret;
cvec *qvec = NULL;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
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);
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, qvec)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
if (qvec)
cvec_free(qvec);
return; /* void */
}
/*! /.well-known callback
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
*/
static void
restconf_path_wellknown(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
evhtp_connection_t *conn;
int ret;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
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);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, NULL)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
return; /* void */
}
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4757
*/
static int
init_openssl(void)
{
int retval = -1;
/* In Openssl 1.1 lib inits itself (?)
* eg SSL_load_error_strings();
*/
/* This isn't strictly necessary... OpenSSL performs RAND_poll
* automatically on first use of random number generator. */
if (RAND_poll() != 1) {
clicon_err(OE_SSL, errno, "Random generator has not been seeded with enough data");
goto done;
}
retval = 0;
done:
return retval;
}
/*!
* The verify_callback function is used to control the behaviour when the SSL_VERIFY_PEER flag
* is set. It must be supplied by the application and receives two arguments: preverify_ok
* indicates, whether the verification of the certificate in question was passed
* (preverify_ok=1) or not (preverify_ok=0). x509_ctx is a pointer to the complete context
* used for the certificate chain verification.
*/
static int
restconf_verify_certs(int preverify_ok,
evhtp_x509_store_ctx_t *store)
{
char buf[256];
X509 *err_cert;
int err;
int depth;
// SSL *ssl;
// clicon_handle h;
clicon_debug(1, "%s %d", __FUNCTION__, preverify_ok);
err_cert = X509_STORE_CTX_get_current_cert(store);
err = X509_STORE_CTX_get_error(store);
depth = X509_STORE_CTX_get_error_depth(store);
// ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
clicon_debug(1, "%s preverify_ok:%d err:%d depth:%d", __FUNCTION__, preverify_ok, err, depth);
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
switch (err){
case X509_V_ERR_HOSTNAME_MISMATCH:
clicon_debug(1, "%s X509_V_ERR_HOSTNAME_MISMATCH", __FUNCTION__);
break;
}
/* Catch a too long certificate chain. should be +1 in SSL_CTX_set_verify_depth() */
if (depth > 1) {
preverify_ok = 0;
err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
X509_STORE_CTX_set_error(store, err);
}
if (depth == VERIFY_DEPTH){
/* Verify the CA name */
}
// h = SSL_get_app_data(ssl);
return preverify_ok;
}
/*! Debug print of all incoming alpn alternatives, eg h2 and http/1.1
*/
static int
dump_alpn_proto_list(const unsigned char *in,
unsigned int inlen)
{
unsigned char *inp;
unsigned char len;
char *str;
inp = (unsigned char*)in;
while ((len = *inp) != 0) {
inp++;
if ((str = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
return -1;
}
strncpy(str, (const char*)inp, len);
str[len] = '\0';
clicon_debug(1, "%s %s", __FUNCTION__, str);
free(str);
inp += len;
}
return 0;
}
/*! Application-layer Protocol Negotiation (alpn) callback
* The value of the out, outlen vector should be set to the value of a single protocol selected from
* the in, inlen vector. The out buffer may point directly into in, or to a buffer that outlives the
* handshake.
*/
static int
alpn_select_proto_cb(SSL *ssl,
const unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg)
{
unsigned char *inp;
unsigned char len;
if (clicon_debug_get())
dump_alpn_proto_list(in, inlen);
/* select http/1.1 */
inp = (unsigned char*)in;
while ((len = *inp) != 0) {
inp++;
if (len == 8 && strncmp((char*)inp, "http/1.1", len) == 0)
// if (len == 2 && strncmp((char*)inp, "h2", len) == 0)
break;
inp += len;
}
if (len == 0)
return SSL_TLSEXT_ERR_NOACK;
*outlen = len;
*out = inp;
return SSL_TLSEXT_ERR_OK;
}
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4794
*/
static SSL_CTX *
restconf_ssl_context_create(clicon_handle h)
{
const SSL_METHOD *method;
SSL_CTX *ctx = NULL;
method = TLS_server_method();
if ((ctx = SSL_CTX_new(method)) == NULL) {
clicon_err(OE_SSL, 0, "SSL_CTX_new");
goto done;
}
/* Options
* As of OpenSSL 1.0.2g the SSL_OP_NO_SSLv2 option is set by default. *
* It is recommended that applications should set SSL_OP_NO_SSLv3.
* The question is which TLS to disable
* SSL_OP_NO_TLSv1, SSL_OP_NO_TLSv1_1, SSL_OP_NO_TLSv1_2
*/
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
SSL_CTX_set_options(ctx, SSL_MODE_RELEASE_BUFFERS | SSL_OP_NO_COMPRESSION);
// SSL_CTX_set_timeout(ctx, cfg->ssl_ctx_timeout); /* default 300s */
/* Application Layer Protocol Negotiation (alpn) callback */
SSL_CTX_set_alpn_select_cb(ctx, alpn_select_proto_cb, h);
done:
return ctx;
}
/*
* 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
* @param[in] server_ca_cert_path CA cert Only if auth-type = client cert
* @see restconf_ssl_context_create
*/
static int
restconf_ssl_context_configure(clixon_handle h,
SSL_CTX *ctx,
const char *server_cert_path,
const char *server_key_path,
const char *server_ca_cert_path)
{
int retval = -1;
SSL_CTX_set_ecdh_auto(ctx, 1);
/* Specifies the locations where CA certificates are located. The certificates available via
* CAfile and CApath are trusted.
* retval= 0: The operation failed because CAfile and CApath are NULL or the
* processing at one of the locations specified failed. Check the error stack to
* find out the reason.
*/
if (server_ca_cert_path){
if (SSL_CTX_load_verify_locations(ctx, server_ca_cert_path, NULL) != 1){
clicon_err(OE_SSL, 0, "SSL_CTX_load_verify_locations(%s)", server_ca_cert_path);
goto done;
}
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER /*| SSL_VERIFY_FAIL_IF_NO_PEER_CERT */,
restconf_verify_certs);
SSL_CTX_set_verify_depth(ctx, VERIFY_DEPTH+1);
}
X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), 0);
SSL_CTX_set_session_id_context(ctx, (void *)&session_id_context, sizeof(session_id_context));
SSL_CTX_set_app_data(ctx, h);
SSL_CTX_set_session_cache_mode(ctx, 0);
/* Set the key and cert */
if (SSL_CTX_use_certificate_chain_file(ctx, server_cert_path) != 1) {
ERR_print_errors_fp(stderr);
goto done;
}
if (SSL_CTX_use_PrivateKey_file(ctx,
server_key_path,
SSL_FILETYPE_PEM) <= 0 ) {
ERR_print_errors_fp(stderr);
goto done;
}
retval = 0;
done:
return retval;
}
/*! Free clixon/cbuf resources related to an evhtp connection
*/
static int
restconf_conn_free(evhtp_connection_t *conn)
{
restconf_conn_h *rc;
if ((rc = conn->arg) != NULL){
if (rc->rc_outp_hdrs)
cvec_free(rc->rc_outp_hdrs);
if (rc->rc_outp_buf)
cbuf_free(rc->rc_outp_buf);
free(rc);
}
return 0;
}
/*! Utility function to close restconf server ssl/evhtp socket.
* There are many variants to closing, one could probably make this more generic
* and always use this function, but it is difficult.
*/
static int
close_ssl_evhtp_socket(int s,
evhtp_connection_t *conn,
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);
clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e);
goto done;
}
SSL_free(ssl);
}
if (close(s) < 0){
clicon_err(OE_UNIX, errno, "close");
goto done;
}
clixon_event_unreg_fd(s, restconf_connection);
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
*/
static int
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: %lu\r\n", strlen(body));
}
else
cprintf(cb, "Content-Length: 0\r\n");
cprintf(cb, "\r\n");
if (body)
cprintf(cb, "%s\r\n", body);
if (buf_write(cbuf_get(cb), cbuf_len(cb), s, ssl) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! New data connection after accept, receive and reply on data sockte
*
* @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: either evhtp 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.
*/
static int
restconf_connection(int s,
void *arg)
{
int retval = -1;
evhtp_connection_t *conn = NULL;
ssize_t n;
char buf[BUFSIZ]; /* from stdio.h, typically 8K */
clicon_handle h;
int readmore = 1;
restconf_conn_h *rc;
cbuf *cberr = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((conn = (evhtp_connection_t*)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
h = (clicon_handle*)conn->htp->arg;
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){
/* 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){
clicon_err(OE_XML, errno, "SSL_read");
goto done;
}
}
else{
if ((n = read(conn->sock, 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);
goto ok; /* Close socket and ssl */
}
clicon_err(OE_XML, errno, "read");
goto done;
}
}
if (n == 0){
clicon_debug(1, "%s n=0 closing socket", __FUNCTION__);
if (close_ssl_evhtp_socket(s, conn, 1) < 0)
goto done;
goto ok;
}
/* parse incoming packet
* signature:
*/
if (connection_parse_nobev(buf, n, conn) < 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",
"protocolmalformed-messageThe requested URL or a header is in some way badly formed") < 0)
goto done;
SSL_free(conn->ssl);
if (close(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);
goto ok;
}
clicon_debug(1, "%s connection_parse OK", __FUNCTION__);
if (conn->bev != NULL){
struct evbuffer *ev;
size_t buflen;
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 ((buflen = evbuffer_get_length(ev)) != 0){
// assert(0); /* 100 Cont ? */
buf = (char*)evbuffer_pullup(ev, -1);
if (cbuf_append_buf(rc->rc_outp_buf, buf, buflen) < 0){
clicon_err(OE_UNIX, errno, "cbuf_append_buf");
goto done;
}
}
}
if (buf_write(cbuf_get(rc->rc_outp_buf), cbuf_len(rc->rc_outp_buf), conn->sock, conn->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",
"protocolmalformed-messageNo evhtp output") < 0)
goto done;
}
} /* while moredata */
ok:
retval = 0;
done:
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
return retval;
}
#if 0 /* debug */
/*! Debug print all loaded certs
*/
static int
restconf_listcerts(SSL *ssl)
{
X509 *cert;
char *line;
clicon_debug(1, "%s get peer certificates:", __FUNCTION__);
if ((cert = SSL_get_peer_certificate(ssl)) != NULL) { /* Get certificates (if available) */
if ((line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) != NULL){
clicon_debug(1, "Subject: %s", line);
free(line);
}
if ((line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0)) != NULL){
clicon_debug(1, "Issuer: %s", line);
free(line);
}
if ((line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) != NULL){
clicon_debug(1, "Subject: %s", line);
free(line);
}
X509_free(cert);
}
return 0;
}
#endif/* debug */
/*! Check if a "cert" file exists
*
* @param[in] xrestconf XML tree containing restconf config
* @param[in] name Name of configured "cert" name
* @param[out] var String variable
* @retval 0 OK, exists
* @retval -1 No, does not exist
*/
static int
restconf_checkcert_file(cxobj *xrestconf,
const char *name,
char **var)
{
int retval = -1;
cxobj *x;
struct stat fstat;
cvec *nsc = NULL;
char *filename;
if ((x = xpath_first(xrestconf, nsc, "%s", name)) == NULL){
clicon_err(OE_FATAL, EFAULT, "cert '%s' not found in config", name);
goto done;
}
if ((filename = xml_body(x)) == NULL){
clicon_err(OE_FATAL, EFAULT, "cert '%s' NULL value in config", name);
goto done;
}
if (stat(filename, &fstat) < 0) {
clicon_err(OE_FATAL, errno, "cert '%s'", filename);
goto done;
}
*var = filename;
retval = 0;
done:
return retval;
}
/*! Accept new socket client
* @param[in] fd Socket (unix or ip)
* @param[in] arg typecast clicon_handle
* @see openssl_init_socket where this callback is registered
*/
static int
restconf_accept_client(int fd,
void *arg)
{
int retval = -1;
restconf_socket *rsock = (restconf_socket *)arg;
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;
clicon_debug(1, "%s %d", __FUNCTION__, fd);
if (rsock == NULL){
clicon_err(OE_YANG, EINVAL, "rsock is NULL");
goto done;
}
h = rsock->rs_h;
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done;
}
len = sizeof(from);
if ((s = accept(rsock->rs_ss, &from, &len)) < 0){
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");
goto done;
}
clicon_debug(1, "%s conn-new (%p)", __FUNCTION__, conn);
if (rsock->rs_ssl){
if ((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);
/* 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);
#if 0
/* Enable this 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
L = Stockholm
O = Clixon
OU = clixon
CN = ca <---
emailAddress = olof@hagsand.se
*/
if (SSL_set1_host(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 */
clicon_err(OE_SSL, 0, "SSL_set1_host");
goto done;
}
#endif
conn->ssl = ssl; /* evhtp */
if (SSL_set_fd(ssl, s) != 1){
clicon_err(OE_SSL, 0, "SSL_set_fd");
goto done;
}
readmore = 1;
while (readmore){
readmore = 0;
/* 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) {
clicon_debug(1, "%s SSL_accept() ret:%d errno:%d", __FUNCTION__, ret, er=errno);
e = SSL_get_error(ssl, ret);
switch (e){
case SSL_ERROR_SSL: /* 1 */
clicon_debug(1, "%s SSL_ERROR_SSL (non-ssl message on ssl socket)", __FUNCTION__);
if (send_badrequest(h, s, NULL, "application/yang-data+xml",
"protocolmalformed-messageThe plain HTTP request was sent to HTTPS port") < 0)
goto done;
SSL_free(ssl);
if (close(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 */
goto ok;
break;
case SSL_ERROR_SYSCALL: /* 5 */
/* Some non-recoverable, fatal I/O error occurred. The OpenSSL error queue
may contain more information on the error. For socket I/O on Unix systems,
consult errno for details. If this error occurs then no further I/O
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)
goto done;
goto ok;
break;
case SSL_ERROR_WANT_READ: /* 2 */
case SSL_ERROR_WANT_WRITE: /* 3 */
/* 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 write SSL_ERROR_WANT_READ", __FUNCTION__);
usleep(10000);
readmore = 1;
break;
case SSL_ERROR_NONE: /* 0 */
case SSL_ERROR_ZERO_RETURN: /* 6 */
case SSL_ERROR_WANT_CONNECT: /* 7 */
case SSL_ERROR_WANT_ACCEPT: /* 8 */
case SSL_ERROR_WANT_X509_LOOKUP: /* 4 */
case SSL_ERROR_WANT_ASYNC: /* 8 */
case SSL_ERROR_WANT_ASYNC_JOB: /* 10 */
#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB
case SSL_ERROR_WANT_CLIENT_HELLO_CB: /* 11 */
#endif
default:
clicon_err(OE_SSL, 0, "SSL_accept:%d", e);
goto done;
break;
}
}
} /* while(readmore) */
/* For client-cert authentication, check if any certs are present,
* if not, send bad request
* Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
* but then SSL_accept fails.
*/
if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){
if ((peercert = SSL_get_peer_certificate(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)
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);
}
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);
if (alpn == NULL) {
/* Returns a pointer to the selected protocol in data with length len. */
SSL_get0_alpn_selected(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_err(OE_RESTCONF, 0, "Protocol http/1.1 not selected: %s", alpn);
goto done;
}
/* 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 (peername != NULL) {
/* Name checks were in scope and matched the peername */
clicon_debug(1, "%s peername:%s", __FUNCTION__, peername);
}
}
#if 0 /* debug */
if (clicon_debug_get())
restconf_listcerts(ssl);
#endif
}
/*
* 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;
}
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)
goto done;
ok:
retval = 0;
done:
clicon_debug(1, "%s retval %d", __FUNCTION__, retval);
if (name)
free(name);
return retval;
}
static int
restconf_native_terminate(clicon_handle h)
{
restconf_native_handle *rh;
restconf_socket *rsock;
clicon_debug(1, "%s", __FUNCTION__);
if ((rh = restconf_native_handle_get(h)) != NULL){
while ((rsock = rh->rh_sockets) != NULL){
clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client);
close(rsock->rs_ss);
DELQ(rsock, rh->rh_sockets, restconf_socket *);
free(rsock);
}
if (rh->rh_ctx)
SSL_CTX_free(rh->rh_ctx);
if (rh->rh_evhtp){
if (rh->rh_evhtp->evbase)
event_base_free(rh->rh_evhtp->evbase);
evhtp_free(rh->rh_evhtp);
}
free(rh);
}
EVP_cleanup();
return 0;
}
/*! Query backend of config.
* Loop to wait for backend starting, try again if not done
* @param[out] xrestconf XML restconf config, malloced (if retval = 1)
* @retval 1 OK (and xrestconf set)
* @retval 0 Fail - no config
* @retval -1 Error
*/
static int
restconf_clixon_backend(clicon_handle h,
cxobj **xrestconfp)
{
int retval = -1;
uint32_t id = 0; /* Session id, to poll backend up */
cvec *nsc = NULL;
struct passwd *pw;
cxobj *xconfig = NULL;
cxobj *xrestconf = NULL;
cxobj *xerr;
int ret;
/* Loop to wait for backend starting, try again if not done */
while (1){
if (clicon_hello_req(h, &id) < 0){
if (errno == ENOENT){
fprintf(stderr, "waiting");
sleep(1);
continue;
}
clicon_err(OE_UNIX, errno, "clicon_session_id_get");
goto done;
}
clicon_session_id_set(h, id);
break;
}
if ((nsc = xml_nsctx_init(NULL, CLIXON_RESTCONF_NS)) == NULL)
goto done;
if ((pw = getpwuid(getuid())) == NULL){
clicon_err(OE_UNIX, errno, "getpwuid");
goto done;
}
/* XXX xconfig leaked */
if (clicon_rpc_get_config(h, pw->pw_name, "running", "/restconf", nsc, &xconfig) < 0)
goto done;
if ((xerr = xpath_first(xconfig, NULL, "/rpc-error")) != NULL){
clixon_netconf_error(xerr, "Get backend restconf config", NULL);
goto done;
}
/* Extract restconf configuration */
if ((xrestconf = xpath_first(xconfig, nsc, "restconf")) == NULL)
goto fail;
if ((ret = restconf_config_init(h, xrestconf)) < 0)
goto done;
if (ret == 0)
goto fail;
if (xml_rm(xrestconf) < 0)
goto done;
*xrestconfp = xrestconf;
retval = 1;
done:
if (nsc)
cvec_free(nsc);
if (xconfig)
xml_free(xconfig);
return retval;
fail:
retval = 0;
goto done;
}
/*! Per-socket openssl inits
* @param[in] h Clicon handle
* @param[in] xs XML config of single restconf socket
* @param[in] nsc Namespace context
*/
static int
openssl_init_socket(clicon_handle h,
cxobj *xs,
cvec *nsc)
{
int retval = -1;
char *netns = NULL;
char *address = NULL;
char *addrtype = NULL;
uint16_t ssl = 0;
uint16_t port = 0;
int ss = -1;
restconf_native_handle *rh = NULL;
restconf_socket *rsock = NULL; /* openssl per socket struct */
clicon_debug(1, "%s", __FUNCTION__);
/* Extract socket parameters from single socket config: ns, addr, port, ssl */
if (restconf_socket_extract(h, xs, nsc, &netns, &address, &addrtype, &port, &ssl) < 0)
goto done;
/* Open restconf socket and bind */
if (restconf_socket_init(netns, address, addrtype, port,
SOCKET_LISTEN_BACKLOG,
#ifdef RESTCONF_OPENSSL_NONBLOCKING
SOCK_NONBLOCK, /* Also 0 is possible */
#else /* blocking */
0,
#endif
&ss
) < 0)
goto done;
if ((rh = restconf_native_handle_get(h)) == NULL){
clicon_err(OE_XML, EFAULT, "No openssl handle");
goto done;
}
/*
* Create per-socket openssl handle
*/
if ((rsock = malloc(sizeof *rsock)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(rsock, 0, sizeof *rsock);
rsock->rs_h = h;
rsock->rs_ss = ss;
rsock->rs_ssl = ssl;
INSQ(rsock, rh->rh_sockets);
/* ss is a server socket that the clients connect to. The callback
therefore accepts clients on ss */
if (clixon_event_reg_fd(ss, restconf_accept_client, rsock, "restconf socket") < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Init openssl, open and register server socket (ready for accept)
* @param[in] h Clicon handle
* @param[in] dbg0 Manually set debug flag, if set overrides configuration setting
* @param[in] xrestconf XML tree containing restconf config
* @retval 0 OK
* @retval -1 Error
*/
int
restconf_openssl_init(clicon_handle h,
int dbg0,
cxobj *xrestconf)
{
int retval = -1;
SSL_CTX *ctx; /* SSL context */
cxobj *x;
cvec *nsc = NULL;
int ssl_enable = 0;
char *server_cert_path = NULL;
char *server_key_path = NULL;
char *server_ca_cert_path = NULL;
restconf_native_handle *rh;
clixon_auth_type_t auth_type;
int dbg;
char *bstr;
cxobj **vec = NULL;
size_t veclen;
int i;
evhtp_t *evhtp = NULL;
struct event_base *evbase = NULL;
clicon_debug(1, "%s", __FUNCTION__);
/* flag used for sanity of certs */
ssl_enable = xpath_first(xrestconf, nsc, "socket[ssl='true']") != NULL;
/* Auth type set in config */
auth_type = restconf_auth_type_get(h);
/* Only set debug from config if not set manually */
if (dbg0 == 0 &&
(x = xpath_first(xrestconf, nsc, "debug")) != NULL &&
(bstr = xml_body(x)) != NULL){
dbg = atoi(bstr);
clicon_debug_init(dbg, NULL);
/* If debug was enabled here from config and not initially,
* print clixn options and loaded yang files
*/
if (dbg) {
clicon_option_dump(h, dbg);
yang_spec_dump(clicon_dbspec_yang(h), dbg);
}
}
if ((x = xpath_first(xrestconf, nsc, "enable-core-dump")) != NULL) {
/* core dump is enabled on RESTCONF process */
struct rlimit rlp;
if (strcmp(xml_body(x), "true") == 0) {
rlp.rlim_cur = RLIM_INFINITY;
rlp.rlim_max = RLIM_INFINITY;
} else {
rlp.rlim_cur = 0;
rlp.rlim_max = 0;
}
int status = setrlimit(RLIMIT_CORE, &rlp);
if (status != 0) {
clicon_log(LOG_NOTICE, "%s: setrlimit() failed, %s", __func__, strerror(errno));
}
}
if (init_openssl() < 0)
goto done;
if ((ctx = restconf_ssl_context_create(h)) == NULL)
goto done;
/* Check certs */
if (ssl_enable){
if (restconf_checkcert_file(xrestconf, "server-cert-path", &server_cert_path) < 0)
goto done;
if (restconf_checkcert_file(xrestconf, "server-key-path", &server_key_path) < 0)
goto done;
if (auth_type == CLIXON_AUTH_CLIENT_CERTIFICATE)
if (restconf_checkcert_file(xrestconf, "server-ca-cert-path", &server_ca_cert_path) < 0)
goto done;
if (restconf_ssl_context_configure(h, ctx, server_cert_path, server_key_path, server_ca_cert_path) < 0)
goto done;
}
rh = restconf_native_handle_get(h);
rh->rh_ctx = ctx;
/* 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_evhtp = 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;
}
/* get the list of socket config-data */
if (xpath_vec(xrestconf, nsc, "socket", &vec, &veclen) < 0)
goto done;
for (i=0; ica_extension = restconf_main_extension_cb;
/* Load Yang modules
* 1. Load a yang module as a specific absolute filename */
if ((str = clicon_yang_main_file(h)) != NULL){
if (yang_spec_parse_file(h, str, yspec) < 0)
goto done;
}
/* 2. Load a (single) main module */
if ((str = clicon_yang_module_main(h)) != NULL){
if (yang_spec_parse_module(h, str, clicon_yang_module_revision(h),
yspec) < 0)
goto done;
}
/* 3. Load all modules in a directory */
if ((str = clicon_yang_main_dir(h)) != NULL){
if (yang_spec_load_dir(h, str, yspec) < 0)
goto done;
}
/* Load clixon lib yang module */
if (yang_spec_parse_module(h, "clixon-lib", NULL, yspec) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
/* Load yang restconf module */
if (yang_spec_parse_module(h, "ietf-restconf", NULL, yspec)< 0)
goto done;
/* Add netconf yang spec, used as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
/* Add system modules */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
yang_spec_parse_module(h, "ietf-restconf-monitoring", NULL, yspec)< 0)
goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done;
/* Here all modules are loaded
* Compute and set canonical namespace context
*/
if (xml_nsctx_yangspec(yspec, &nsctx_global) < 0)
goto done;
if (clicon_nsctx_global_set(h, nsctx_global) < 0)
goto done;
ret = 0;
if (clicon_option_bool(h, "CLICON_BACKEND_RESTCONF_PROCESS") == 0){
/* If not read from backend, try to get restconf config from local config-file */
if ((xrestconf = clicon_conf_restconf(h)) != NULL){
/*! Basic config init, set auth-type, pretty, etc ret 0 means disabled */
if ((ret = restconf_config_init(h, xrestconf)) < 0)
goto done;
/* ret == 1 means this config is OK */
if (ret == 0){
xrestconf = NULL; /* Dont free since it is part of conf tree */
}
else
if ((*xrestconfp = xml_dup(xrestconf)) == NULL)
goto done;
}
}
/* If no local config, or it is disabled, try to query backend of config.
*/
else {
if ((ret = restconf_clixon_backend(h, xrestconfp)) < 0)
goto done;
if (ret == 0)
goto fail;
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Signal terminates process
* Just set exit flag for proper exit in event loop
*/
static void
restconf_sig_term(int arg)
{
static int i=0;
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
__PROGRAM__, __FUNCTION__, getpid(), arg);
if (i++ > 0) /* Allow one sigterm before proper exit */
exit(-1);
/* This should ensure no more accepts or incoming packets are processed because next time eventloop
* is entered, it will terminate.
* However there may be a case of sockets closing rather abruptly for clients
*/
clicon_exit_set();
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
*/
static void
usage(clicon_handle h,
char *argv0)
{
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\t Help\n"
"\t-D \t Debug level, overrides any config debug setting\n"
"\t-f \t Configuration file (mandatory)\n"
"\t-E \t Extra configuration file directory\n"
"\t-l > \t Log on (s)yslog, (f)ile (syslog is default)\n"
"\t-p \t Yang directory path (see CLICON_YANG_DIR)\n"
"\t-y \t Load yang spec file (override yang main module)\n"
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
"\t-u \t Internal socket domain path or IP addr (see -a)\n"
"\t-r \t\t Do not drop privileges if run as root\n"
"\t-o