/*
 *
  ***** 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  clicon_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
  * 
  * <html>
  * <head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
  * <body>
  * <center><h1>400 Bad Request</h1></center>
  * <center>The plain HTTP request was sent to HTTPS port</center>
  * <hr><center>nginx</center>
  * </body>
  * </html>
  *
  * 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
  * <html>
  * <head><title>400 No required SSL certificate was sent</title></head>
  * <body>
  * <center><h1>400 Bad Request</h1></center>
  * <center>No required SSL certificate was sent</center>
  * <hr><center>nginx/1.16.1</center>
  * </body>
  * </html>
  *
  * 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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/resource.h>

#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>

#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif

/* cligen */
#include <cligen/cligen.h>

/* libclixon */
#include <clixon/clixon.h>

/* restconf */
#include "restconf_lib.h"       /* generic shared with plugins */
#include "restconf_handle.h"
#include "restconf_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

/* 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

static int             session_id_context = 1;

/*! 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;
}

/* 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;
}

/*! 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) {
        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;
    case X509_V_ERR_CERT_HAS_EXPIRED:
        clicon_debug(1, "%s X509_V_ERR_CERT_HAS_EXPIRED", __FUNCTION__);
        break;
    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
        clicon_debug(1, "%s X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT", __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);
    /* 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,
                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_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(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;
}

/*
 * @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;
}

#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;
    clicon_handle           h;
    int                     s;
    struct sockaddr         from = {0,};
    socklen_t               len;
    char                   *name = NULL;
    void                   *addr;

    clicon_debug(1, "%s %d", __FUNCTION__, fd);
    if ((rsock = (restconf_socket *)arg) == NULL){
        clicon_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){
        clicon_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){
        clicon_err(OE_UNIX, errno, "calloc");
        goto done;
    }
    if (inet_ntop(from.sa_family, addr, rsock->rs_from_addr, INET6_ADDRSTRLEN) < 0)
        goto done;
    clicon_debug(1, "%s type:%s from:%s, dest:%s port:%hu", __FUNCTION__,
                 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;
    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 *rn;
    restconf_socket        *rsock;
    restconf_conn          *rc;

    clicon_debug(1, "%s", __FUNCTION__);
    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(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, "cl:restconf", NULL, &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, NULL, &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
 * @retval     0        OK
 * @retval     -1       Error
 */
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        port = 0;
    int             ss = -1;
    restconf_native_handle *rn = NULL;
    restconf_socket *rsock = NULL; /* openssl per socket struct */
    struct timeval   now;

    clicon_debug(1, "%s", __FUNCTION__);
    /*
     * 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;
    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){
            clicon_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){
        clicon_err(OE_XML, EFAULT, "No openssl handle");
        goto done;
    }
    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, rn->rn_sockets);

    if (rsock->rs_callhome){
        rsock->rs_ss = -1; /* Not applicable from callhome */
        if (restconf_callhome_timer(rsock, 0) < 0)
            goto done;
    }
    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;
    }
    retval = 0;
 done:
    return retval;
}

/*! Init openssl, open and register server socket (ready for accept)
 *
 * Given a fully populated configuration tree.
 * @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   *rn;
    clixon_auth_type_t auth_type;
    int                dbg;
    char              *bstr;
    cxobj            **vec = NULL;
    size_t             veclen;
    int                i;

    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
         */
        clicon_option_dump(h, 1);
    }
    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;
    }
    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; i<veclen; i++){
        if (openssl_init_socket(h, vec[i], nsc) < 0){
            /* Bind errors are ignored, proceed with next after log */
            if (clicon_errno == OE_UNIX && clicon_suberrno == EADDRNOTAVAIL)
                clicon_err_reset();
            else
                goto done;
        }
    }
    retval = 1;
 done:
    if (vec)
        free(vec);
    return retval;
}

/*! Read restconf from config 
 *
 * After SEVERAL iterations the code now does as follows:
 * - init clixon
 * - look for local config (in clixon-config file) 
 * - if local config found, open sockets accordingly and exit function
 * - If no local config found, query backend for config and open sockets.
 * That is, EITHER local config OR read config from backend once
 * @param[in]  h             Clicon handle
 * @param[in]  inline_config If set, restconf conf is given by -R command-line
 * @param[out] xrestconf     XML restconf config, malloced (if retval = 1)
 * @retval     1             OK  (and xrestconf set)
 * @retval     0             Fail - no config
 * @retval    -1             Error
 */ 
int
restconf_clixon_init(clicon_handle h,
                     char         *inline_config,
                     cxobj       **xrestconfp)
{
    int            retval = -1;
    char          *dir;
    size_t         cligen_buflen;
    size_t         cligen_bufthreshold;
    yang_stmt     *yspec = NULL;
    clixon_plugin_t *cp = NULL;
    char          *str;
    cvec          *nsctx_global = NULL; /* Global namespace context */
    cxobj         *xrestconf = NULL;
    cxobj         *xerr = NULL;
    int            ret;
    size_t         sz;

    /* Set default namespace according to CLICON_NAMESPACE_NETCONF_DEFAULT */
    xml_nsctx_namespace_netconf_default(h);

    /* Init cligen buffers */
    cligen_buflen = clicon_option_int(h, "CLICON_CLI_BUF_START");
    cligen_bufthreshold = clicon_option_int(h, "CLICON_CLI_BUF_THRESHOLD");
    cbuf_alloc_set(cligen_buflen, cligen_bufthreshold);

    if ((sz = clicon_option_int(h, "CLICON_LOG_STRING_LIMIT")) != 0)
        clicon_log_string_limit_set(sz);

    /* Add (hardcoded) netconf features in case ietf-netconf loaded here
     * Otherwise it is loaded in netconf_module_load below
     */
    if (netconf_module_features(h) < 0)
        goto done;
    /* In case ietf-yang-metadata is loaded by application, handle annotation extension */
    if (yang_metadata_init(h) < 0)
        goto done;
    /* Create top-level yang spec and store as option */
    if ((yspec = yspec_new()) == NULL)
        goto done;
    clicon_dbspec_yang_set(h, yspec);

    /* Load restconf plugins before yangs are loaded (eg extension callbacks) */
    if ((dir = clicon_restconf_dir(h)) != NULL)
        if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
            return -1;
    /* Create a pseudo-plugin to create extension callback to set the ietf-routing
     * yang-data extension for api-root top-level restconf function.
     */
    if (clixon_pseudo_plugin(h, "pseudo restconf", &cp) < 0)
        goto done;
    clixon_plugin_api_get(cp)->ca_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)){
        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 <level>\t  Debug level, overrides any config debug setting\n"
            "\t-f <file>\t  Configuration file (mandatory)\n"
            "\t-E <dir> \t  Extra configuration file directory\n"
            "\t-l <s|e|o|n|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut, (n)one or (f)ile (syslog is default)\n"
            "\t-p <dir>\t  Yang directory path (see CLICON_YANG_DIR)\n"
            "\t-y <file>\t  Load yang spec file (override yang main module)\n"
            "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
            "\t-u <path|addr>\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 <user>\t  Run restconf daemon as this user, drop according to CLICON_RESTCONF_PRIVILEGES\n"
            "\t-R <xml> \t  Restconf configuration in-line overriding config file\n"
            "\t-o <option>=<value> Set configuration option overriding config file (see clixon-config.yang)\n"
            ,
            argv0
            );
    exit(0);
}

int
main(int    argc,
     char **argv)
{
    int             retval = -1;
    char           *argv0 = argv[0];
    int             c;
    clicon_handle   h;
    int             dbg = 0;
    int             logdst = CLICON_LOG_SYSLOG;
    restconf_native_handle *rn = NULL;
    int             ret;
    cxobj          *xrestconf = NULL;
    char           *inline_config = NULL;

    /* In the startup, logs to stderr & debug flag set later */
    clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
    
    /* Create handle */
    if ((h = restconf_handle_init()) == NULL)
        goto done;

    while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
        switch (c) {
        case 'h':
            usage(h, argv0);
            break;
        case 'D' : /* debug. Note this overrides any setting in the config */
            if (sscanf(optarg, "%d", &dbg) != 1)
                usage(h, argv0);
            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 ((logdst = clicon_log_opt(optarg[0])) < 0)
                usage(h, argv0);
            if (logdst == CLICON_LOG_FILE &&
                strlen(optarg)>1 &&
                clicon_log_file(optarg+1) < 0)
                goto done;
           break;
        } /* switch getopt */

    /* 
     * Logs, error and debug to stderr or syslog, set debug level
     */
    clicon_log_init(__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
    clicon_debug_init(dbg, NULL); 
    clicon_log(LOG_NOTICE, "%s native %u Started", __PROGRAM__, getpid());
    if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
        clicon_err(OE_DAEMON, errno, "Setting signal");
        goto done;
    }
    if (set_signal(SIGINT, restconf_sig_term, NULL) < 0){
        clicon_err(OE_DAEMON, errno, "Setting signal");
        goto done;
    }
    if (set_signal(SIGPIPE, SIG_IGN, NULL) < 0){
        clicon_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 '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 (afetr 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 '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;

    /* 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);
    
    /* Dump configuration options on debug */
    clicon_option_dump(h, 1);

    /* Initialize plugin module by creating a handle holding plugin and callback lists */
    if (clixon_plugin_module_init(h) < 0)
        goto done;
    /* 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, &xrestconf)) < 0)
        goto done;
    if (ret == 0){ /* restconf disabled */
        clicon_debug(1, "restconf configuration not found or disabled");
        retval = 0;
        goto done;
    }
    /* Create and stroe global openssl handle */ 
    if ((rn = malloc(sizeof *rn)) == NULL){
        clicon_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) < 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;
    retval = 0;
 done:
    clicon_debug(1, "restconf_main_openssl done");
    if (xrestconf)
        xml_free(xrestconf);
    restconf_native_terminate(h);
    restconf_terminate(h);
    return retval;
}
