/* * ***** 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 #ifdef HAVE_LIBEVHTP /* evhtp */ #include /* evbuffer */ #define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_EVTHR #include #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 /* nghttp2 */ #include #endif /* 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*/ #ifdef HAVE_LIBEVHTP #include "restconf_evhtp.h" /* http/1 */ #endif #ifdef HAVE_LIBNGHTTP2 #include "restconf_nghttp2.h" /* http/2 */ #endif /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hD:f:E:l:p:y:a:u:rW:R:o:" /* 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; /* Two problems with debugging buffers from libevent 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:%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){ 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[in] suberr Application-specific handle * @param[out] cb Read log/error string into this buffer */ static int clixon_openssl_log_cb(void *handle, int suberr, cbuf *cb) { clicon_debug(1, "%s", __FUNCTION__); ERR_print_errors_cb(print_cb, cb); return 0; } /* * 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, X509_STORE_CTX *store) { char buf[256]; X509 *err_cert; int err; int depth; // SSL *ssl; // clicon_handle h; 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 > VERIFY_DEPTH + 1) { preverify_ok = 0; err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(store, err); } else{ /* 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 alpn_proto_dump(const char *label, const char *inp, int len) { int retval = -1; char *str = NULL; if ((str = malloc(len+1)) == NULL){ clicon_err(OE_UNIX, errno, "malloc"); goto done; } strncpy(str, inp, len); str[len] = '\0'; clicon_debug(1, "%s %s", label, str); retval = 0; done: free(str); return retval; } /*! 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; int pref = 0; clicon_debug(1, "%s", __FUNCTION__); /* select http/1.1 */ inp = (unsigned char*)in; while ((inp-in) < inlen) { len = *inp; inp++; if (clicon_debug_get()) /* debug print the protoocol */ alpn_proto_dump(__FUNCTION__, (const char*)inp, len); #ifdef HAVE_LIBEVHTP if (pref < 10 && len == 8 && strncmp((char*)inp, "http/1.1", len) == 0){ *outlen = len; *out = inp; pref = 10; } #endif #ifdef HAVE_LIBNGHTTP2 /* Higher pref than http/1.1 */ if (pref < 20 && len == 2 && strncmp((char*)inp, "h2", len) == 0){ *outlen = len; *out = inp; pref = 20; } #endif inp += len; } if (pref == 0) return SSL_TLSEXT_ERR_NOACK; alpn_proto_dump("ALPN selected:", (const char*)*out, *outlen); 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_OP_NO_TLSv1_1); 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; } /*! 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. */ int restconf_close_ssl_socket(restconf_conn *rc, int shutdown) { int retval = -1; int ret; if (rc->rc_ssl != NULL){ if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){ #if 0 case SSL_ERROR_ZERO_RETURN: /* 6 */ Note that in this case SSL_ERROR_ZERO_RETURN does not necessarily indicate that the underlying transport has been closed. #endif int e = SSL_get_error(rc->rc_ssl, ret); clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e); goto done; } 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"); goto done; } clixon_event_unreg_fd(rc->rc_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: %zu\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; restconf_conn *rc = NULL; ssize_t n; char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */ int readmore = 1; int sslerr; #ifdef HAVE_LIBNGHTTP2 int ret; #endif #ifdef HAVE_LIBEVHTP clicon_handle h; evhtp_connection_t *evconn = NULL; restconf_stream_data *sd; #endif 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){ /* 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(rc->rc_ssl, buf, sizeof(buf))) < 0){ sslerr = SSL_get_error(rc->rc_ssl, n); clicon_debug(1, "%s SSL_read() n:%zd errno:%d sslerr:%d", __FUNCTION__, n, 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); readmore = 1; break; default: clicon_err(OE_XML, errno, "SSL_read"); goto done; } /* switch */ continue; /* readmore */ } } else{ if ((n = read(rc->rc_s, buf, sizeof(buf))) < 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); goto ok; /* Close socket and ssl */ break; case EAGAIN: clicon_debug(1, "%s read EAGAIN", __FUNCTION__); usleep(1000); readmore = 1; break; default:; clicon_err(OE_XML, errno, "read"); goto done; break; } continue; } } clicon_debug(1, "%s (ssl)read:%zd", __FUNCTION__, n); 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_LIBEVHTP 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 (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(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; } /* 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", "protocolmalformed-messageNo evhtp output") < 0) goto done; } #ifdef HAVE_LIBNGHTTP2 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); } } #endif break; #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 case HTTP_2: 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; goto ok; } /* There may be more data frames */ readmore++; 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 */ #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; } /*! 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 *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_INFO, "%s Warning: %s", __FUNCTION__, 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_INFO, "%s Warning: ALPN: No protocol selected", __FUNCTION__); } 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_INFO, "%s Warning: SSL_shutdown SSL_ERROR_SYSCALL", __FUNCTION__); /* Continue */ } else { clicon_err(OE_SSL, 0, "SSL_shutdown, err:%d", e); goto done; } } SSL_free(rc->rc_ssl); } restconf_conn_free(rc); } retval = 0; /* ALPN not OK */ done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (cberr) cbuf_free(cberr); return retval; } /* ssl_alpn_check */ /*! 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_native_handle *rh = NULL; restconf_conn *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; 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); #ifdef HAVE_LIBNGHTTP2 #ifndef HAVE_LIBEVHTP 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"); 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"); goto done; } len = sizeof(from); if ((s = accept(rsock->rs_ss, &from, &len)) < 0){ clicon_err(OE_UNIX, errno, "accept"); goto done; } /* * Register callbacks for actual data socket */ if ((rc = restconf_conn_new(h, s)) == NULL) goto done; clicon_debug(1, "%s s:%d", __FUNCTION__, rc->rc_s); if (rsock->rs_ssl){ 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__, 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(rc->rc_ssl, X509_CHECK_FLAG_NO_WILDCARDS); #if 0 /* 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 L = Stockholm O = Clixon OU = clixon CN = ca <--- emailAddress = olof@hagsand.se */ 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(rc->rc_ssl, "olof") != 1) { /* for peer cert */ clicon_err(OE_SSL, 0, "SSL_set1_host"); goto done; } #endif if (SSL_set_fd(rc->rc_ssl, rc->rc_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(rc->rc_ssl)) != 1) { clicon_debug(1, "%s SSL_accept() ret:%d errno:%d", __FUNCTION__, ret, er=errno); 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 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(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; } restconf_conn_free(rc); 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 (restconf_close_ssl_socket(rc, 0) < 0) goto done; restconf_conn_free(rc); rc = NULL; 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; } } /* SSL_accept */ } /* while(readmore) */ /* Sets data and len to point to the client's requested protocol for this connection. */ #ifndef OPENSSL_NO_NEXTPROTONEG SSL_get0_next_proto_negotiated(rc->rc_ssl, &alpn, &alpnlen); #endif /* !OPENSSL_NO_NEXTPROTONEG */ if (alpn == NULL) { /* Returns a pointer to the selected protocol in data with length len. */ SSL_get0_alpn_selected(rc->rc_ssl, &alpn, &alpnlen); } 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)); #if 0 /* Seems too early to fail here, instead let authentication callback deal with this */ /* 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){ X509 *peercert; if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){ X509_free(peercert); } else { /* Get certificates (if available) */ if (proto != HTTP_2 && send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", "protocolmalformed-messagePeer certificate required") < 0) goto done; 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(rc->rc_ssl); rc->rc_ssl = NULL; } goto ok; } } #endif /* 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(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 */ clicon_debug(1, "%s peername:%s", __FUNCTION__, peername); } } #if 0 /* debug */ if (clicon_debug_get()) 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_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 */ /* Create a default stream for http/1 */ if (restconf_stream_data_new(rc, 0) == NULL) goto done; } break; #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 case HTTP_2:{ if (http2_session_init(rc) < 0){ restconf_close_ssl_socket(rc, 1); goto done; } if (http2_send_server_connection(rc) < 0){ restconf_close_ssl_socket(rc, 1); #ifdef NYI if (ssl) { SSL_shutdown(ssl); } bufferevent_free(session_data->bev); nghttp2_session_del(session_data->session); for (stream_data = session_data->root.next; stream_data;) { http2_stream_data *next = stream_data->next; delete_http2_stream_data(stream_data); stream_data = next; } free(session_data->client_addr); free(session_data); #endif goto done; } break; } #endif /* HAVE_LIBNGHTTP2 */ 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; done: clicon_debug(1, "%s retval %d", __FUNCTION__, retval); if (name) free(name); return retval; } /* restconf_accept_client */ 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 *); 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 { 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(); 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 * See restconf_native_terminate for freeing */ 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; 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 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; #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 */ 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_INFO, "%s: setrlimit() failed, %s", __FUNCTION__, 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; #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; 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; #ifdef YANG_PATCH /* Load yang restconf patch module */ if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) goto done; #endif // YANG_PATCH /* 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; if (inline_config != NULL && strlen(inline_config)){ clicon_debug(1, "%s reading from inline config", __FUNCTION__); if ((ret = clixon_xml_parse_string(inline_config, YB_MODULE, yspec, &xrestconf, &xerr)) < 0) goto done; if (ret == 0){ clixon_netconf_error(xerr, "Inline restconf config", NULL); goto done; } /* Replace parent w first child */ if (xml_rootchild(xrestconf, 0, &xrestconf) < 0) goto done; if ((ret = restconf_config_init(h, xrestconf)) < 0) goto done; /* ret == 1 means this config is OK */ if (ret == 0){ xml_free(xrestconf); xrestconf = NULL; } else if ((*xrestconfp = xrestconf) == NULL) goto done; } else if (clicon_option_bool(h, "CLICON_BACKEND_RESTCONF_PROCESS") == 0){ clicon_debug(1, "%s reading from clixon config", __FUNCTION__); /* 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 { clicon_debug(1, "%s reading from backend datastore config", __FUNCTION__); if ((ret = restconf_clixon_backend(h, xrestconfp)) < 0) goto done; if (ret == 0) goto fail; } retval = 1; done: if (xerr) xml_free(xerr); 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 */ clixon_exit_set(1); } /*! 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-W \t Run restconf daemon as this user, drop according to CLICON_RESTCONF_PRIVILEGES\n" "\t-R \t Restconf configuration in-line overriding config file\n" "\t-o