/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * libssl 1.1 API * Data structures: * 1 1 * +--------------------+ restconf_handle_get +--------------------+ * | rn restconf_native | <--------------------- | h clixon_handle | * | _handle | +--------------------+ * +--------------------+ ^ * common SSL config \ | * \ | n * \ rn_sockets +--------------------+ * +-----------> | rs restconf_socket | * +--------------------+ * per-server socket (per config) * | ^ * rs_conns v | n * +--------------------+ * | rc restconf_conn | * +--------------------+ * per-connection (transient) * n * +--------------------+ * | rr restconf_request| per-packet * +--------------------+ * Note restconf_request may need to be extended eg backpointer to rs? * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBNGHTTP2 #include #endif /* cligen */ #include /* libclixon */ #include /* 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_LIBNGHTTP2 #include "restconf_nghttp2.h" /* http/2 */ #endif #ifdef HAVE_HTTP1 #include "restconf_http1.h" #endif #ifndef SOCK_NONBLOCK #include #define SOCK_NONBLOCK O_NONBLOCK #endif /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hVD:f:E:l:C:p:y:a:u:rW:R:t: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 static int session_id_context = 1; /*! Set restconf native handle * * @param[in] h Clixon handle * @param[in] rh Restconf native handle (malloced pointer) * @retval 0 OK * @retval -1 Error */ static int restconf_native_handle_set(clixon_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; } /*! 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) { clixon_debug(CLIXON_DBG_RESTCONF, ""); ERR_print_errors_cb(print_cb, cb); return 0; } /*! Init openSSL */ 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) { clixon_err(OE_SSL, errno, "Random generator has not been seeded with enough data"); goto done; } retval = 0; done: return retval; } /*! Verify cert * * 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; // clixon_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()); clixon_debug(CLIXON_DBG_RESTCONF, "preverify_ok:%d err:%d depth:%d", preverify_ok, err, depth); X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256); switch (err){ case X509_V_ERR_HOSTNAME_MISMATCH: clixon_debug(CLIXON_DBG_RESTCONF, "X509_V_ERR_HOSTNAME_MISMATCH"); break; case X509_V_ERR_CERT_HAS_EXPIRED: clixon_debug(CLIXON_DBG_RESTCONF, "X509_V_ERR_CERT_HAS_EXPIRED"); break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: clixon_debug(CLIXON_DBG_RESTCONF, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"); 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); /* Different schemes for return values if failure detected: * - 0 (preferity_ok) the session terminates here in SSL negotiation, an http client * will get a low level error (not http reply) * - 1 Check if the cert is valid using SSL_get_verify_result(rc->rc_ssl) * @see restconf_nghttp2_sanity where this is done for http/1 and http/2 */ return 1; } /*! Debug print of all incoming alpn alternatives, eg h2 and http/1.1 */ static int alpn_proto_dump(const char *label, const char *inp, unsigned len) { clixon_debug(CLIXON_DBG_RESTCONF, "%s %.*s", label, (int)len, inp); 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; int pref = 0; clixon_debug(CLIXON_DBG_RESTCONF, ""); /* select http/1.1 */ inp = (unsigned char*)in; while ((inp-in) < inlen) { len = *inp; inp++; if (clixon_debug_get()) /* debug print the protoocol */ alpn_proto_dump(__FUNCTION__, (const char*)inp, len); #ifdef HAVE_HTTP1 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; } /* */ static SSL_CTX * restconf_ssl_context_create(clixon_handle h) { const SSL_METHOD *method; SSL_CTX *ctx = NULL; method = TLS_server_method(); if ((ctx = SSL_CTX_new(method)) == NULL) { clixon_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; } /* * @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){ clixon_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; } #if 0 /* debug */ /*! Debug print all loaded certs */ static int restconf_listcerts(SSL *ssl) { X509 *cert; char *line; clixon_debug(CLIXON_DBG_RESTCONF, "get peer certificates:"); 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){ clixon_debug(CLIXON_DBG_RESTCONF, "Subject: %s", line); free(line); } if ((line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0)) != NULL){ clixon_debug(CLIXON_DBG_RESTCONF, "Issuer: %s", line); free(line); } if ((line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0)) != NULL){ clixon_debug(CLIXON_DBG_RESTCONF, "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){ clixon_err(OE_FATAL, EFAULT, "cert '%s' not found in config", name); goto done; } if ((filename = xml_body(x)) == NULL){ clixon_err(OE_FATAL, EFAULT, "cert '%s' NULL value in config", name); goto done; } if (stat(filename, &fstat) < 0) { clixon_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 clixon_handle * @retval 0 OK * @retval -1 Error * @see openssl_init_socket where this callback is registered */ static int restconf_accept_client(int fd, void *arg) { int retval = -1; restconf_socket *rsock; clixon_handle h; int s = -1; struct sockaddr from = {0,}; socklen_t len; char *name = NULL; void *addr; clixon_debug(CLIXON_DBG_RESTCONF, "%d", fd); if ((rsock = (restconf_socket *)arg) == NULL){ clixon_err(OE_YANG, EINVAL, "rsock is NULL"); goto done; } h = rsock->rs_h; len = sizeof(from); if ((s = accept(rsock->rs_ss, &from, &len)) < 0){ clixon_err(OE_UNIX, errno, "accept"); goto done; } switch (from.sa_family){ case AF_INET:{ struct sockaddr_in *in = (struct sockaddr_in *)&from; addr = &(in->sin_addr); break; } case AF_INET6:{ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&from; addr = &(in6->sin6_addr); break; } default: errno = EAFNOSUPPORT; goto done; } if (rsock->rs_from_addr != NULL){ free(rsock->rs_from_addr); rsock->rs_from_addr = NULL; } if ((rsock->rs_from_addr = calloc(INET6_ADDRSTRLEN, 1)) == NULL){ clixon_err(OE_UNIX, errno, "calloc"); goto done; } if (inet_ntop(from.sa_family, addr, rsock->rs_from_addr, INET6_ADDRSTRLEN) == NULL) goto done; clixon_debug(CLIXON_DBG_RESTCONF, "type:%s from:%s, dest:%s port:%hu", rsock->rs_addrtype, rsock->rs_from_addr, rsock->rs_addrstr, rsock->rs_port); clicon_data_set(h, "session-source-host", rsock->rs_from_addr); /* Accept SSL */ if (restconf_ssl_accept_client(h, s, rsock, NULL) < 0) goto done; s = -1; retval = 0; done: clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); if (name) free(name); if (s != -1) close(s); return retval; } /* restconf_accept_client */ /*! */ static int restconf_native_terminate(clixon_handle h) { restconf_native_handle *rn; restconf_socket *rsock; restconf_conn *rc; clixon_debug(CLIXON_DBG_RESTCONF, ""); if ((rn = restconf_native_handle_get(h)) != NULL){ while ((rsock = rn->rn_sockets) != NULL){ while ((rc = rsock->rs_conns) != NULL){ if (rc->rc_s != -1){ clixon_event_unreg_fd(rc->rc_s, restconf_connection); close(rc->rc_s); } DELQ(rc, rsock->rs_conns, restconf_conn *); restconf_close_ssl_socket(rc, __FUNCTION__, 0); } if (rsock->rs_callhome){ restconf_callhome_timer_unreg(rsock); } else if (rsock->rs_ss != -1){ clixon_event_unreg_fd(rsock->rs_ss, restconf_accept_client); close(rsock->rs_ss); } DELQ(rsock, rn->rn_sockets, restconf_socket *); if (rsock->rs_description) free(rsock->rs_description); if (rsock->rs_addrstr) free(rsock->rs_addrstr); if (rsock->rs_addrtype) free(rsock->rs_addrtype); if (rsock->rs_from_addr) free(rsock->rs_from_addr); free(rsock); } if (rn->rn_ctx) SSL_CTX_free(rn->rn_ctx); free(rn); } EVP_cleanup(); return 0; } /*! Query backend of config. * * Loop to wait for backend starting, try again if not done * @param[in] h Clixon handle * @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(clixon_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, "cl:restconf", NULL, &id) < 0){ if (errno == ENOENT){ clixon_err(OE_UNIX, errno, "waiting"); sleep(1); continue; } clixon_err(OE_UNIX, errno, "clicon_session_id_get"); goto done; } clicon_session_id_set(h, id); break; } /* XXX HARDCODED NAMESPACE NEEDS GENERIC MECHANISM * It works if one uses "import clixon-restconf" * But not if one does uses clixon-restdonf in which case the namespace is * the local (top-level). */ if ((nsc = xml_nsctx_init(NULL, CLIXON_RESTCONF_NS)) == NULL) goto done; if ((pw = getpwuid(getuid())) == NULL){ clixon_err(OE_UNIX, errno, "getpwuid"); goto done; } if (clicon_rpc_get_config(h, pw->pw_name, "running", "/restconf", nsc, NULL, &xconfig) < 0) goto done; if ((xerr = xpath_first(xconfig, NULL, "/rpc-error")) != NULL){ if (clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get backend restconf config") < 0) goto done; 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 Clixon handle * @param[in] xs XML config of single restconf socket * @param[in] nsc Namespace context * @param[in] timeout Terminate notification event stream after number of seconds * @retval 0 OK * @retval -1 Error */ static int openssl_init_socket(clixon_handle h, cxobj *xs, cvec *nsc, int timeout) { int retval = -1; char *netns = NULL; char *address = NULL; char *addrtype = NULL; uint16_t port = 0; int ss = -1; restconf_native_handle *rn = NULL; restconf_socket *rsock = NULL; /* openssl per socket struct */ struct timeval now; clixon_debug(CLIXON_DBG_RESTCONF, ""); /* * Create per-socket openssl handle * See restconf_native_terminate for freeing */ if ((rsock = malloc(sizeof *rsock)) == NULL){ clixon_err(OE_UNIX, errno, "malloc"); goto done; } memset(rsock, 0, sizeof *rsock); rsock->rs_h = h; rsock->rs_stream_timeout = timeout; gettimeofday(&now, NULL); rsock->rs_start = now.tv_sec; /* Extract socket parameters from single socket config: ns, addr, port, ssl */ if (restconf_socket_extract(h, xs, nsc, rsock, &netns, &address, &addrtype, &port) < 0) goto done; if (rsock->rs_callhome){ if (!rsock->rs_ssl){ clixon_err(OE_SSL, EINVAL, "Restconf callhome requires SSL"); goto done; } } else { /* listen/accept */ /* Open restconf socket and bind for later accept */ 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 ((rn = restconf_native_handle_get(h)) == NULL){ clixon_err(OE_XML, EFAULT, "No openssl handle"); goto done; } if ((rsock->rs_addrstr = strdup(address)) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } if ((rsock->rs_addrtype = strdup(addrtype)) == NULL){ clixon_err(OE_UNIX, errno, "strdup"); goto done; } rsock->rs_port = port; INSQ(rsock, rn->rn_sockets); if (rsock->rs_callhome){ rsock->rs_ss = -1; /* Not applicable from callhome */ if (restconf_callhome_timer(rsock, 0) < 0) goto done; rsock = NULL; } else { /* ss is a server socket that the clients connect to. The callback therefore accepts clients on ss */ rsock->rs_ss = ss; if (clixon_event_reg_fd(rsock->rs_ss, restconf_accept_client, rsock, "restconf socket") < 0) goto done; rsock = NULL; } retval = 0; done: if (rsock) free(rsock); return retval; } /*! Init openssl, open and register server socket (ready for accept) * * Given a fully populated configuration tree. * @param[in] h Clixon handle * @param[in] dbg0 Manually set debug flag, if set overrides configuration setting * @param[in] xrestconf XML tree containing restconf config * @param[in] timeout0 Command-line timeout (overrides if set config timeout) * @retval 0 OK * @retval -1 Error */ static int restconf_openssl_init(clixon_handle h, int dbg0, cxobj *xrestconf, int timeout0) { 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 *rn; clixon_auth_type_t auth_type; int dbg; char *bstr; cxobj **vec = NULL; size_t veclen; int i; int timeout = 0; clixon_debug(CLIXON_DBG_RESTCONF, ""); /* 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); /* Timeout */ if ((x = xpath_first(xrestconf, nsc, "timeout")) != NULL && (bstr = xml_body(x)) != NULL){ timeout = atoi(bstr); } /* Command-line overrides */ if (timeout0 != 0) timeout = timeout0; /* 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); clixon_debug_init(h, dbg); /* If debug was enabled here from config and not initially, * print clixn options and loaded yang files */ clicon_option_dump(h, CLIXON_DBG_INIT); } if ((x = xpath_first(xrestconf, nsc, "enable-core-dump")) != NULL) { /* core dump is enabled on RESTCONF process */ struct rlimit rlp; int status; 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; } status = setrlimit(RLIMIT_CORE, &rlp); if (status != 0) { clixon_log(h, 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; } rn = restconf_native_handle_get(h); rn->rn_ctx = ctx; /* 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 CLIXON_YANG_PATCH /* Load yang restconf patch module */ if (yang_spec_parse_module(h, "ietf-yang-patch", NULL, yspec)< 0) goto done; #endif /* 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)){ clixon_debug(CLIXON_DBG_RESTCONF, "reading from inline config"); if ((ret = clixon_xml_parse_string(inline_config, YB_MODULE, yspec, &xrestconf, &xerr)) < 0) goto done; if (ret == 0){ clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Inline restconf config"); 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){ clixon_debug(CLIXON_DBG_RESTCONF, "reading from clixon config"); /* 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 { clixon_debug(CLIXON_DBG_RESTCONF, "reading from backend datastore config"); if ((ret = restconf_clixon_backend(h, xrestconfp)) < 0) goto done; if (ret == 0) goto fail; } ok: 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; clixon_log(NULL, 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 Clixon handle */ static void usage(clixon_handle h, char *argv0) { fprintf(stderr, "usage:%s [options]\n" "where options are\n" "\t-h \t\t Help\n" "\t-V \t\t Print version and exit\n" "\t-D \t Debug level (see available levels below)\n" "\t-f \t Configuration file (mandatory)\n" "\t-E \t Extra configuration file directory\n" "\t-l > \tLog on (s)yslog, std(e)rr, std(o)ut, (n)one or (f)ile (syslog is default)\n" "\t-C \t Dump configuration options on stdout after loading and exit. Format is xml|json|text\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-t \t Notification stream timeout in: quit after . For debug\n" "\t-o =\t Set configuration option overriding config file (see clixon-config.yang)\n" , argv0 ); fprintf(stderr, "Debug keys: "); clixon_debug_key_dump(stderr); fprintf(stderr, "\n"); exit(0); } /* Clixon native restconf application main entry point */ int main(int argc, char **argv) { int retval = -1; char *argv0 = argv[0]; int c; clixon_handle h; int dbg = 0; int logdst = CLIXON_LOG_SYSLOG; restconf_native_handle *rn = NULL; int ret; cxobj *xrestconf = NULL; char *inline_config = NULL; int config_dump = 0; enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; int stream_timeout = 0; int32_t d; /* Create handle */ if ((h = restconf_handle_init()) == NULL) goto done; /* In the startup, logs to stderr & debug flag set later */ if (clixon_log_init(h, __PROGRAM__, LOG_INFO, logdst) < 0) goto done; if (clixon_err_init(h) < 0) goto done; while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1) switch (c) { case 'h': usage(h, argv0); break; case 'V': /* version */ cligen_output(stdout, "Clixon version: %s\n", CLIXON_VERSION); print_version++; /* plugins may also print versions w ca-version callback */ break; case 'D' : /* debug. Note this overrides any setting in the config */ /* Try first symbolic, then numeric match */ if ((d = clixon_debug_str2key(optarg)) < 0 && sscanf(optarg, "%d", &d) != 1){ usage(h, argv[0]); } dbg |= d; break; case 'f': /* override config file */ if (!strlen(optarg)) usage(h, argv0); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; case 'E': /* extra config directory */ if (!strlen(optarg)) usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; case 'l': /* Log destination: s|e|o */ if ((d = clixon_logdst_str2key(optarg)) < 0){ if (optarg[0] == 'f'){ /* Check for special -lf syntax */ d = CLIXON_LOG_FILE; if (strlen(optarg) > 1 && clixon_log_file(optarg+1) < 0) goto done; } else usage(h, argv[0]); } logdst = d; break; } /* switch getopt */ /* * Logs, error and debug to stderr or syslog, set debug level */ clixon_log_init(h, __PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst); /* * Register error category and error/log callbacks for openssl special error handling */ if (clixon_err_cat_reg(OE_SSL, /* category */ h, /* handle (can be NULL) */ clixon_openssl_log_cb /* log fn */ ) < 0) goto done; #ifdef HAVE_LIBNGHTTP2 /* * Register error category and error/log callbacks for openssl special error handling */ if (clixon_err_cat_reg(OE_NGHTTP2, /* category */ h, /* handle (can be NULL) */ clixon_nghttp2_log_cb /* log fn */ ) < 0) goto done; #endif clixon_debug_init(h, dbg); clixon_log(h, LOG_NOTICE, "%s native %u Started, SSL version:%lx", __PROGRAM__, getpid(), OPENSSL_VERSION_NUMBER); if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){ clixon_err(OE_DAEMON, errno, "Setting signal"); goto done; } if (set_signal(SIGINT, restconf_sig_term, NULL) < 0){ clixon_err(OE_DAEMON, errno, "Setting signal"); goto done; } if (set_signal(SIGPIPE, SIG_IGN, NULL) < 0){ clixon_err(OE_DAEMON, errno, "Setting signal"); goto done; } yang_init(h); /* Find and read configfile */ if (clicon_options_main(h) < 0) goto done; // stream_path = clicon_option_str(h, "CLICON_STREAM_PATH"); /* Now rest of options, some overwrite option file */ optind = 1; opterr = 0; while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1) switch (c) { case 'h' : /* help */ case 'D' : /* debug */ case 'f': /* config file */ case 'E': /* extra config dir */ case 'l': /* log */ break; /* see above */ case 'C': /* Explicitly dump configuration */ if ((config_dump_format = format_str2int(optarg)) == (enum format_enum)-1){ fprintf(stderr, "Unrecognized dump format: %s(expected: xml|json|text)\n", argv[0]); usage(h, argv[0]); } config_dump++; break; case 'p' : /* yang dir path */ if (clicon_option_add(h, "CLICON_YANG_DIR", optarg) < 0) goto done; break; case 'y' : /* Load yang spec file (override yang main module) */ clicon_option_str_set(h, "CLICON_YANG_MAIN_FILE", optarg); break; case 'a': /* internal backend socket address family */ clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg); break; case 'u': /* internal backend socket unix domain path or ip host */ if (!strlen(optarg)) usage(h, argv0); clicon_option_str_set(h, "CLICON_SOCK", optarg); break; case 'r': /* Do not drop privileges if run as root */ if (clicon_option_add(h, "CLICON_RESTCONF_PRIVILEGES", "none") < 0) goto done; break; case 'W': /* Run restconf daemon as this user (after drop) */ if (clicon_option_add(h, "CLICON_RESTCONF_USER", optarg) < 0) goto done; break; case 'R': /* Restconf on-line config */ inline_config = optarg; break; case 't': /* Stream timeout */ stream_timeout = atoi(optarg); break; case 'o':{ /* Configuration option */ char *val; if ((val = index(optarg, '=')) == NULL) usage(h, argv0); *val++ = '\0'; if (clicon_option_add(h, optarg, val) < 0) goto done; break; } default: usage(h, argv0); break; } argc -= optind; argv += optind; /* Read debug and log options from config file if not given by command-line */ if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) goto done; /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); /* Init restconf auth-type */ restconf_auth_type_set(h, CLIXON_AUTH_NONE); /* Explicit dump of config (also debug dump below). */ if (config_dump){ if (clicon_option_dump1(h, stdout, config_dump_format, 1) < 0) goto done; goto ok; } /* Dump configuration options on debug */ clicon_option_dump(h, CLIXON_DBG_INIT); /* Initialize plugin module by creating a handle holding plugin and callback lists */ if (clixon_plugin_module_init(h) < 0) goto done; yang_start(h); /* Call start function in all plugins before we go interactive */ if (clixon_plugin_start_all(h) < 0) goto done; /* Clixon inits / configs */ if ((ret = restconf_clixon_init(h, inline_config, print_version, &xrestconf)) < 0) goto done; if (ret == 0){ /* restconf disabled */ clixon_log(h, LOG_INFO, "restconf configuration not found or disabled"); retval = 0; goto done; } /* Create and store global openssl handle */ if ((rn = malloc(sizeof *rn)) == NULL){ clixon_err(OE_UNIX, errno, "malloc"); goto done; } memset(rn, 0, sizeof *rn); if (restconf_native_handle_set(h, rn) < 0) goto done; /* Openssl inits */ if (restconf_openssl_init(h, dbg, xrestconf, stream_timeout) < 0) goto done; /* Drop privileges if started as root to CLICON_RESTCONF_USER * and use drop mode: CLICON_RESTCONF_PRIVILEGES */ if (restconf_drop_privileges(h) < 0) goto done; /* Set RFC6022 session parameters that will be sent in first hello, * @see clicon_hello_req */ clicon_data_set(h, "session-transport", "cl:restconf"); /* Main event loop */ if (clixon_event_loop(h) < 0) goto done; ok: retval = 0; done: clixon_debug(CLIXON_DBG_RESTCONF, "restconf_main_openssl done"); if (xrestconf) xml_free(xrestconf); restconf_native_terminate(h); restconf_terminate(h); return retval; }