/*------------------------------------------------------------------
 * estserver.c - Example application that utilizes libest.so for
 *               EST server operations.  libest does not manage
 *               sockets and pthreads.  This responsibility is
 *               placed on the application.  This module shows
 *               a fairly trivial example of how to setup a
 *               listening socket and server EST requests.
 *
 * November, 2012
 *
 * Copyright (c) 2012-2013, 2016, 2017, 2018 by cisco Systems, Inc.
 * All rights reserved.
 **------------------------------------------------------------------
 */
#include <stdio.h>
#ifndef WIN32
#include <pthread.h>
#endif
#include <stdint.h>
#ifndef DISABLE_TSEARCH
#include <search.h>
#endif
#include <getopt.h>
#include <openssl/err.h>
#include <openssl/engine.h>
#include <openssl/conf.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <openssl/pem.h>
#include <openssl/md5.h>
#include <est.h>
#include "ossl_srv.h"
#include "../util/utils.h"
#include "../util/simple_server.h"
#include "../util/jsmn.h"

/*
 * Abstract OpenSSL threading platform callbacks
 */
#ifdef WIN32
#define MUTEX_TYPE            HANDLE
#define MUTEX_SETUP(x)        (x) = CreateMutex(NULL, FALSE, NULL)
#define MUTEX_CLEANUP(x)      CloseHandle(x)
#define MUTEX_LOCK(x)         WaitForSingleObject((x), INFINITE)
#define MUTEX_UNLOCK(x)       ReleaseMutex(x)
#define THREAD_ID             GetCurrentThreadId()
#define snprintf _snprintf
#else
#define MUTEX_TYPE            pthread_mutex_t
#define MUTEX_SETUP(x)        pthread_mutex_init(&(x), NULL)
#define MUTEX_CLEANUP(x)      pthread_mutex_destroy(&(x))
#define MUTEX_LOCK(x)         pthread_mutex_lock(&(x))
#define MUTEX_UNLOCK(x)       pthread_mutex_unlock(&(x))
#define THREAD_ID             pthread_self()
#endif

#define MAX_SERVER_LEN 255
#define MAX_FILENAME_LEN 255
#define MAX_REALM_LEN    32
#define DEFAULT_ENHCD_CERT_PWD "cisco"
#define DEFAULT_ENHCD_CERT_LOCAL_PKI_NID NID_commonName

/*
 * The OpenSSL CA needs this BIO to send errors too
 */
BIO *bio_err = NULL;

/*
 * These are the command line options with defaults provided below
 */
static int verbose = 0;
static int write_csr = 0;
static int crl = 0;
static int pop = 0;
static int v6 = 0;
static int srp = 0;
static int enforce_csr = 0;
static int manual_enroll = 0;
int coap_mode = 0;
#if HAVE_LIBCOAP
static int dtls_handshake_timeout = EST_DTLS_HANDSHAKE_TIMEOUT_DEF;
static int dtls_handshake_mtu = EST_DTLS_HANDSHAKE_MTU_DEF;
static int dtls_session_max = EST_DTLS_SESSION_MAX_DEF;
#endif
static int port_num = 8085;
static int http_digest_auth = 0;
static int http_basic_auth = 0;
static int http_token_auth = 0;
static int http_auth_disable = 0;
static int disable_forced_http_auth = 0;
static int enable_enhcd_cert_auth = 0;
static int set_cert_auth_ah_pwd = 0;
static EST_ECA_CSR_CHECK_FLAG enhcd_cert_csr_check_on = ECA_CSR_CHECK_OFF;
static int set_cert_auth_local_nid= 0;
static int set_cert_auth_mfg_name = 0;
static int set_enhcd_cert_truststore = 0;
static int set_cert_auth_mfg_nid = 0;
static int set_fips_return = 0;
static unsigned long set_fips_error = 0;
static int test_app_data = 0xDEADBEEF;
static char priv_key_pwd[MAX_PWD_LEN];
#if ENABLE_BRSKI
static int brski_mode = 0;
static int brski_ca_certs_len;
static unsigned char *brski_ca_certs;
static char masa_root_ca_file[EST_MAX_FILE_LEN + 1];
static char masa_priv_key_file[EST_MAX_FILE_LEN + 1];

static X509 *masa_ca_root;
static EVP_PKEY *masa_ca_priv_key;
static int masa_ca_enabled = 0;
#endif
static int perf_timers_on = 0;

char certfile[EST_MAX_FILE_LEN];
char keyfile[EST_MAX_FILE_LEN];
char cert_auth_ah_pwd[MAX_PWD_LEN + 1];
char local_nid[MAX_PWD_LEN + 1];
char mfg_name[MFG_NAME_MAX_LEN + 1];
char mfg_truststore_file[EST_MAX_FILE_LEN];
char mfg_nid[MAX_PWD_LEN + 1];
char realm[MAX_REALM];
unsigned char *cacerts_raw = NULL;
int cacerts_len = 0;
unsigned char *trustcerts = NULL;
int trustcerts_len = 0;
unsigned char *enhcd_cert_truststore = NULL;
int enhcd_cert_truststore_len = 0;

SRP_VBASE *srp_db = NULL;

static char valid_token_value[MAX_AUTH_TOKEN_LEN + 1];

/*
 * This is the single EST context we need for operating
 * the EST server.  Only a single context is required.
 */
EST_CTX *ectx;

/*
 * We hard-code the DH parameters here.  THIS SHOULD NOT
 * be done in a real application.  The DH parameters need
 * to be generated at the time of product installation so
 * that every instance of the product in the field has
 * unique parameters.  Otherwise DH key exchange would be
 * vulnerable to attack.
 * See the OpenSSL documentation on generating DH parameters
 * for more information.
 */
static DH *get_dh1024dsa ()
{
    static unsigned char dh1024_p[]={
    0xC8,0x00,0xF7,0x08,0x07,0x89,0x4D,0x90,0x53,0xF3,0xD5,0x00,
    0x21,0x1B,0xF7,0x31,0xA6,0xA2,0xDA,0x23,0x9A,0xC7,0x87,0x19,
    0x3B,0x47,0xB6,0x8C,0x04,0x6F,0xFF,0xC6,0x9B,0xB8,0x65,0xD2,
    0xC2,0x5F,0x31,0x83,0x4A,0xA7,0x5F,0x2F,0x88,0x38,0xB6,0x55,
    0xCF,0xD9,0x87,0x6D,0x6F,0x9F,0xDA,0xAC,0xA6,0x48,0xAF,0xFC,
    0x33,0x84,0x37,0x5B,0x82,0x4A,0x31,0x5D,0xE7,0xBD,0x52,0x97,
    0xA1,0x77,0xBF,0x10,0x9E,0x37,0xEA,0x64,0xFA,0xCA,0x28,0x8D,
    0x9D,0x3B,0xD2,0x6E,0x09,0x5C,0x68,0xC7,0x45,0x90,0xFD,0xBB,
    0x70,0xC9,0x3A,0xBB,0xDF,0xD4,0x21,0x0F,0xC4,0x6A,0x3C,0xF6,
    0x61,0xCF,0x3F,0xD6,0x13,0xF1,0x5F,0xBC,0xCF,0xBC,0x26,0x9E,
    0xBC,0x0B,0xBD,0xAB,0x5D,0xC9,0x54,0x39,
    };
    static unsigned char dh1024_g[]={
    0x3B,0x40,0x86,0xE7,0xF3,0x6C,0xDE,0x67,0x1C,0xCC,0x80,0x05,
    0x5A,0xDF,0xFE,0xBD,0x20,0x27,0x74,0x6C,0x24,0xC9,0x03,0xF3,
    0xE1,0x8D,0xC3,0x7D,0x98,0x27,0x40,0x08,0xB8,0x8C,0x6A,0xE9,
    0xBB,0x1A,0x3A,0xD6,0x86,0x83,0x5E,0x72,0x41,0xCE,0x85,0x3C,
    0xD2,0xB3,0xFC,0x13,0xCE,0x37,0x81,0x9E,0x4C,0x1C,0x7B,0x65,
    0xD3,0xE6,0xA6,0x00,0xF5,0x5A,0x95,0x43,0x5E,0x81,0xCF,0x60,
    0xA2,0x23,0xFC,0x36,0xA7,0x5D,0x7A,0x4C,0x06,0x91,0x6E,0xF6,
    0x57,0xEE,0x36,0xCB,0x06,0xEA,0xF5,0x3D,0x95,0x49,0xCB,0xA7,
    0xDD,0x81,0xDF,0x80,0x09,0x4A,0x97,0x4D,0xA8,0x22,0x72,0xA1,
    0x7F,0xC4,0x70,0x56,0x70,0xE8,0x20,0x10,0x18,0x8F,0x2E,0x60,
    0x07,0xE7,0x68,0x1A,0x82,0x5D,0x32,0xA2,
    };
    DH *dh;
#ifndef HAVE_OLD_OPENSSL
    BIGNUM *p, *g;
#endif
    
    if ((dh = DH_new()) == NULL) {
        return (NULL);
    }
#ifdef HAVE_OLD_OPENSSL
    dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
    dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
    if ((dh->p == NULL) || (dh->g == NULL)) {
        DH_free(dh);
        return (NULL);
    }
    dh->length = 160;
    return (dh);
#else    
    p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
    g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
    if ((p == NULL) || (g == NULL)) {
        DH_free(dh);
        return (NULL);
    }
    DH_set0_pqg(dh, p, NULL, g);
    return (dh);
#endif    
}

static int string_password_cb (char *buf, int size, int wflag, void *data)
{
    strncpy(buf,priv_key_pwd, size);
    return(strnlen(buf, size));
}

static void print_version (FILE *fp)
{
    fprintf(fp, "Using %s\n", SSLeay_version(SSLEAY_VERSION));
}

static void show_usage_and_exit (void)
{
    fprintf(stderr, "\nAvailable EST server options\n"
            "  -v           Verbose operation\n"
            "  -c <file>    PEM file to use for server cert\n"
            "  -k <file>    PEM file to use for server key\n"
            "  -r <value>   HTTP realm to present to clients. Max is 32 characters.\n"
            "  -l           Enable CRL checks\n"
            "  -t           Enable check for binding client PoP to the TLS UID\n"
            "  -m <seconds> Simulate manual CA enrollment\n"
            "  -n           Disable HTTP authentication (TLS client auth required)\n"
            "  -o           Disable HTTP authentication when TLS client auth succeeds\n"
            "  -h           Use HTTP Digest auth instead of Basic auth\n"
            "  -b           Use HTTP Basic auth.  Causes explicit call to set Basic auth\n"
            "  -p <num>     TCP port number to listen on\n"
#ifndef DISABLE_PTHREADS
            "  -d <seconds> Sleep timer to auto-shut the server\n"
#endif
            "  -f           Runs EST Server in FIPS MODE = ON\n"
            "  -6           Enable IPv6\n"
            "  -w           Dump the CSR to '/tmp/csr.p10' allowing for manual attribute capture on server\n"
            "  -?           Print this help message and exit\n"
            "  --keypass_stdin Specify en-/decryption of private key, password read from STDIN\n"
            "  --keypass_arg   Specify en-/decryption of private key, password read from argument\n"
            "  --srp <file> Enable TLS-SRP authentication of client using the specified SRP parameters file\n"
            "  --enforce-csr  Enable CSR attributes enforcement. The client must provide all the attributes in the CSR.\n"
            "  --token <value> Use HTTP Bearer Token auth.\n"
            "  --enhcd_cert_auth        Enable Enhanced Certificate Auth mode\n"
            "  --enhcd_cert_local_nid <nid> Sets the local PKI domain subject field NID to \n"
            "                               grab from the peer cert. If not set the\n"
            "                               commonName NID will be used\n"
            "  --cert_auth_ah_pwd <value> Specify the auth header password to use\n"
            "                             in Enhanced Certificate Auth mode\n"
            "  --cert_auth_csr_check_on     Enable the CSR check during Enhanced Cert Auth\n"
            "  --enhcd_cert_mfg_name <name> Sets name of the manufacturer to be registered\n"
            "                               This name is required when registering a manufacturer\n"
            "  --enhcd_cert_mfg_truststore <file> Specifies a truststore file for an Enhanced\n"
            "                                     Certificate Auth manufacturer to select the\n"
            "                                     subject field based upon. This truststore is\n"
            "                                     required when registering a manufacturer\n"
            "  --enhcd_cert_mfg_nid <nid> Sets the subject field NID to\n"
            "                             grab from the peer cert when that cert came\n"
            "                             from the manufacturer. If not set the\n"
            "                             commonName NID will be used\n"
#if ENABLE_BRSKI
            "  --enable-brski Enable BRSKI bootstrapping support.\n"
#endif            
#ifdef HAVE_LIBCOAP
            "  --enable-coap Enable EST over CoAP support.\n"
            "  --dtls-handshake-timeout Set the intial value of the DTLS handshake timeout.\n"
            "  --dtls-handshake-mtu Set the MTU used during DTLS handshake phase.\n"
            "  --dtls-session-max Set the maximum number of DTLS sessions.\n"
#endif
            "  --perf-timers-on  Enable the performace timers in server\n"
            "\n");
    exit(255);
}

#ifndef DISABLE_TSEARCH
/*
 * The functions in this section implement a simple lookup table
 * to correlate incoming cert requests after a retry operation.
 * We use this to simulate the manual-enrollment mode on the CA.
 *
 * FIXME: we need a cleanup routine to clear the tree when this
 *        server shuts down.  Currently any remaining entries
 *        in the table will not be released, resulting in a memory
 *        leak in the valgrind output.
 */
typedef struct {
    unsigned char *data;  //this will hold the pub key from the cert request
    int length;
} LOOKUP_ENTRY;
LOOKUP_ENTRY *lookup_root = NULL;

/*
 * Used to compare two entries in the lookup table to correlate
 * incoming cert requests in the case of a retry operation.
 * We use the public key from the cert as the index into the
 * lookup table.
 */
int compare (const void *pa, const void *pb)
{
    LOOKUP_ENTRY *a = (LOOKUP_ENTRY *) pa;
    LOOKUP_ENTRY *b = (LOOKUP_ENTRY *) pb;
    if (a->length > b->length) {
        return 1;
    }
    if (a->length < b->length) {
        return -1;
    }
    return (memcmp(a->data, b->data, a->length));
}

/*
 * We use a simple lookup table to simulate manual enrollment
 * of certs by the CA.  This is the case where an operator
 * needs to review each cert request and approve it (e.g.
 * auto-enrollment is off).
 *
 * Return 1 if a match was found and the enrollment operation
 * should proceed.  Return 0 if no match was found, in which
 * case we'll add the public key from the cert request into
 * our lookup table so it can be correlated later.
 */
int lookup_pkcs10_request (unsigned char *pkcs10, int p10_len)
{
    X509_REQ *req = NULL;
    BIO *in = NULL;
    BIO *out = NULL;
    BIO *b64;
    EVP_PKEY *pkey = NULL;
    BUF_MEM *bptr;
    int rv;
    LOOKUP_ENTRY *l;
    LOOKUP_ENTRY *n;

    /*
     * Decode the request into an X509_REQ structure
     */
    b64 = BIO_new(BIO_f_base64());
    in = BIO_new_mem_buf(pkcs10, p10_len);
    in = BIO_push(b64, in);
    if ((req = d2i_X509_REQ_bio(in, NULL)) == NULL) {
        /* Unable to parse the request, just let this fall through
         * and the enrollment will fail */
        rv = 1;
        goto DONE;
    }

    /*
     * Get the public key from the request, this will be our index into
     * the lookup table.  Frankly, I'm not sure how a real CA
     * would do this lookup.  But this should be good enough for
     * testing the retry-after logic.
     */
#ifdef HAVE_OLD_OPENSSL
    pkey = X509_PUBKEY_get(req->req_info->pubkey);
#else
    pkey = X509_PUBKEY_get(X509_REQ_get_X509_PUBKEY(req));
#endif
    if (!pkey) {
        rv = 1;
        goto DONE;
    }
    out = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(out, pkey);
    BIO_get_mem_ptr(out, &bptr);

    /*
     * see if we can find a match for this public key
     */
    n = malloc(sizeof(LOOKUP_ENTRY));
    n->data = malloc(bptr->length);
    n->length = bptr->length;
    memcpy(n->data, bptr->data, n->length);
    l = tfind(n, (void **) &lookup_root, compare);
    if (l) {
        /* We have a match, allow the enrollment */
        rv = 1;
        tdelete(n, (void **) &lookup_root, compare);
        if (verbose)
            printf("\nRemoving key from lookup table:\n");
        dumpbin((unsigned char*) n->data, n->length);
        free(n->data);
        free(n);
    } else {
        /* Not a match, add it to the list and return */
        l = tsearch(n, (void **) &lookup_root, compare);
        rv = 0;
        if (verbose)
            printf("\nAdding key to lookup table:\n");
        dumpbin((unsigned char*) n->data, n->length);
    }
    DONE: if (out)
        BIO_free_all(out);
    if (in)
        BIO_free_all(in);
    if (req)
        X509_REQ_free(req);
    if (pkey)
        EVP_PKEY_free(pkey);

    return (rv);
}
#else
/*
 * The functions in this section implement a simple lookup table
 * to correlate incoming cert requests after a retry operation
 * without the use of the search library. We use this to simulate
 * the manual-enrollment mode on the CA.
 *
 * FIXME: we need a cleanup routine to clear the tree when this
 *        server shuts down.  Currently any remaining entries
 *        in the table will not be released, resulting in a memory
 *        leak in the valgrind output.
 */
struct lookup_entry {
    unsigned char *data;  //this will hold the pub key from the cert request
    int length;
    struct lookup_entry * next;
};

typedef struct lookup_entry LOOKUP_ENTRY;

/*
 * This is the head of our linked list
 */
struct lookup_entry *lookup_root = NULL;

static int compare(const void *pa, const void *pb)
{
    LOOKUP_ENTRY *a = (LOOKUP_ENTRY *)pa;
    struct lookup_entry *b = (LOOKUP_ENTRY *)pb;
    if (a->length > b->length) return 1;
    if (a->length < b->length) return -1;
    return (memcmp(a->data, b->data, a->length));
}

static void free_lookup(void *node)
{
    LOOKUP_ENTRY *n = (LOOKUP_ENTRY *)node;
    if (n->data) free(n->data);
    free(n);
}

static LOOKUP_ENTRY * search_list(LOOKUP_ENTRY *head, LOOKUP_ENTRY* target) {

    LOOKUP_ENTRY * tmp = head;
    LOOKUP_ENTRY * tmp_prev = NULL;

    while (tmp && compare(tmp, target)) {
        tmp_prev = tmp;
        tmp = tmp->next;
    }

    if (tmp == NULL) {
        return NULL;
    }

    return tmp;
}

static LOOKUP_ENTRY * delete_lookup_entry(LOOKUP_ENTRY *head, LOOKUP_ENTRY * target)
{
    LOOKUP_ENTRY *tmp = head;
    LOOKUP_ENTRY *tmp_prev = NULL;

    /* look for the node that matches d, but also remember the node
     that points to it, tmp_prev, so that we can create a new link
     */

    while (tmp && compare(tmp, target))
    {
        tmp_prev = tmp;
        tmp = tmp->next;
    }

    /* did we fail to find the node? */
    if (tmp == NULL)
    return NULL;

    /* otherwise, remove the node */

    if (tmp == head)
    {
        /* remove head of list */
        head = head->next;
    }
    else
    {
        tmp_prev->next = tmp->next;
    }

    /* free matching node */
    free_lookup(tmp);
    return head;
}

static void add_entry(LOOKUP_ENTRY * head, LOOKUP_ENTRY *new_node) {

    LOOKUP_ENTRY * tmp = head;

    if (tmp->next == NULL) {
        tmp->next = new_node;
    }
    else {
        while (TRUE) {
            if (tmp->next == NULL) {
                tmp->next = new_node;
                break;
            }
            tmp = tmp->next;
        }

    }

}

static void destroy_lookup_table(LOOKUP_ENTRY * head) {

    LOOKUP_ENTRY * tmp;

    while (head) {
        tmp = head;
        head = head->next;
        free_lookup(tmp);
    }

}

/*
 * We use a simple lookup table to simulate manual enrollment
 * of certs by the CA.  This is the case where an operator
 * needs to review each cert request and approve it (e.g.
 * auto-enrollment is off).
 *
 * Return 1 if a match was found and the enrollment operation
 * should proceed.  Return 0 if no match was found, in which
 * case we'll add the public key from the cert request into
 * our lookup table so it can be correlated later.
 *
 * Windows: Rewriting to forgo the use of search.h API
 * lookup table will be implemented as a basic linked list
 */
static int lookup_pkcs10_request(unsigned char *pkcs10, int p10_len)
{
    X509_REQ *req = NULL;
    BIO *in = NULL;
    BIO *out = NULL;
    BIO *b64;
    EVP_PKEY *pkey = NULL;
    BUF_MEM *bptr;
    int rv;
    LOOKUP_ENTRY *l;
    LOOKUP_ENTRY *n;

    /*
     * Decode the request into an X509_REQ structure
     */
    b64 = BIO_new(BIO_f_base64());
    in = BIO_new_mem_buf(pkcs10, p10_len);
    in = BIO_push(b64, in);
    if ((req = d2i_X509_REQ_bio(in, NULL)) == NULL) {
        /* Unable to parse the request, just let this fall through
         * and the enrollment will fail */
        rv = 1;
        goto DONE;
    }

    /*
     * Get the public key from the request, this will be our index into
     * the lookup table.  Frankly, I'm not sure how a real CA
     * would do this lookup.  But this should be good enough for
     * testing the retry-after logic.
     */
    pkey = X509_PUBKEY_get(req->req_info->pubkey);
    if (!pkey) {
        rv = 1;
        goto DONE;
    }
    out = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(out, pkey);
    BIO_get_mem_ptr(out, &bptr);

    /*
     * see if we can find a match for this public key
     */
    n = malloc(sizeof(LOOKUP_ENTRY));
    n->data = malloc(bptr->length);
    n->length = bptr->length;
    memcpy(n->data, bptr->data, n->length);
    n->next = NULL;
    l = search_list(lookup_root, n);
    if (l) {
        /* We have a match, allow the enrollment */
        rv = 1;
        lookup_root = delete_lookup_entry(lookup_root, n);
        printf("\nRemoving key from lookup table:\n");
        dumpbin((char*)n->data, n->length);
        free(n->data);
        free(n);
    }
    else {
        /* Not a match, add it to the list and return */

        if (lookup_root == NULL) {
            /*
             * Initialize the list
             */
            lookup_root = n;
        }
        else {
            add_entry(lookup_root, n);
        }
        rv = 0;
        printf("\nAdding key to lookup table:\n");
        dumpbin((char*)n->data, n->length);
    }
    DONE:
    if (out)
        BIO_free_all(out);
    if (in)
        BIO_free_all(in);
    if (req)
        X509_REQ_free(req);
    if (pkey)
        EVP_PKEY_free(pkey);

    return (rv);
}
#endif

/*
 * Trivial utility function to extract the string
 * value of the subject name from a cert.
 */
static void extract_sub_name (X509 *cert, char *name, int len)
{
    X509_NAME *subject_nm;
    BIO *out;
    BUF_MEM *bm;

    subject_nm = X509_get_subject_name(cert);

    out = BIO_new(BIO_s_mem());

    X509_NAME_print_ex(out, subject_nm, 0, XN_FLAG_SEP_SPLUS_SPC);
    BIO_get_mem_ptr(out, &bm);
    strncpy(name, bm->data, len);
    if (bm->length < len) {
        name[bm->length] = 0;
    } else {
        name[len] = 0;
    }

    BIO_free(out);
}

/****************************************************************************************
 * The following functions are the callbacks used by libest to bind
 * the EST stack to the HTTP/SSL layer and the CA server.
 ***************************************************************************************/
#ifndef WIN32
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
#else
static CRITICAL_SECTION enrollment_critical_section;
#endif
#define MAX_CERT_LEN 8192
/*
 * Callback function used by EST stack to process a PKCS10
 * enrollment request with the CA.  The parameters are:
 *
 *   pkcs10  Contains the CSR that should be sent to
 *              the CA to be signed.
 *   pkcs10_len Length of the CSR char array
 *   pcks7   Should contain the signed PKCS7 certificate
 *              from the CA server.  You'll need allocate
 *              space and copy the cert into this char array.
 *   pkcs7_len  Length of the pkcs7 char array, you will set this.
 *   user_id    If HTTP authentication was used to identify the
 *              EST client, this will contain the user ID supplied
 *              by the client.
 *   peer_cert  If the EST client presented a certificate to identify
 *              itself during the TLS handshake, this parameter will
 *              contain that certificate.
 *   path_seg   If the incoming request contains a path segment it
 *              is extracted from the URI and passed here.  Typically
 *              used to mux between multiple CAs or to identify a
 *              specific profile to use by the CA.
 *   app_data   an optional pointer to information that is to be
 *              used by the application layer.
 *
 */
int process_pkcs10_enrollment (unsigned char * pkcs10, int p10_len,
                               unsigned char **pkcs7, int *pkcs7_len,
                               char *user_id, X509 *peer_cert, char *path_seg,
                               void *app_data)
{
    BIO *result = NULL;
    char *buf;
#ifndef WIN32
    int rc;
#endif
    char sn[64];
    char file_name[MAX_FILENAME_LEN];

    fprintf(stderr, "Entering %s\n", __FUNCTION__);
    
    if (verbose) {
        /*
         * Informational only
         */
        if (user_id) {
            /*
             * Should be safe to log the user ID here since HTTP auth
             * has succeeded at this point.
             */
            printf("\n%s - User ID is %s\n", __FUNCTION__, user_id);
        }
        if (peer_cert) {
            memset(sn, 0, 64);
            extract_sub_name(peer_cert, sn, 64);
            printf("\n%s - Peer cert CN is %s\n", __FUNCTION__, sn);
        }
        if (app_data) {
            printf("ex_data value is %x\n", *((unsigned int *) app_data));
        }
        if (path_seg) {
            printf("\nPath segment was included in enrollment URI. "
                "Path Segment = %s\n", path_seg);
        }
    }

    /*
     * If we're simulating manual certificate enrollment,
     * the CA will not automatically sign the cert request.
     * We'll attempt to lookup in our local table if this
     * cert has already been sent to us, if not, add it
     * to the table and send the 'retry' message back to the
     * client.  But if this cert request has been seen in the
     * past, then we'll continue with the enrollment.
     * To summarize, we're simulating manual enrollment by
     * forcing the client to request twice, and we'll automatically
     * enroll on the second request.
     */
    if (manual_enroll) {
        if (lookup_pkcs10_request(pkcs10, p10_len)) {
            /*
             * We've seen this cert request in the past.
             * Remove it from the lookup table and allow
             * the enrollment to continue.
             * Fall-thru to enrollment logic below
             */
        } else {
            /*
             * Couldn't find this request, it's the first time
             * we've seen it.  Therefore, send the retry
             * response.
             */
            return (EST_ERR_CA_ENROLL_RETRY);
        }

    }

#ifndef WIN32
    rc = pthread_mutex_lock(&m);
    if (rc) {
        printf("\nmutex lock failed rc=%d", rc);
        exit(1);
    }
#else
    EnterCriticalSection(&enrollment_critical_section);
#endif 

    if (write_csr) {
        /*
         * Dump out pkcs10 to a file, this will contain a list of the OIDs in the CSR.
         */
        snprintf(file_name, MAX_FILENAME_LEN, "/tmp/csr.p10");
        write_binary_file(file_name, pkcs10, p10_len);
    }

    result = ossl_simple_enroll(pkcs10, p10_len);
#ifndef WIN32
    rc = pthread_mutex_unlock(&m);
    if (rc) {
        printf("\nmutex unlock failed rc=%d", rc);
        exit(1);
    }
#else
    LeaveCriticalSection(&enrollment_critical_section);
#endif

    /*
     * The result is a BIO containing the pkcs7 signed certificate
     * Need to convert it to char and copy the results so we can
     * free the BIO.
     */
    *pkcs7_len = BIO_get_mem_data(result, (char**) &buf);
    if (*pkcs7_len > 0 && *pkcs7_len < MAX_CERT_LEN) {
        *pkcs7 = malloc(*pkcs7_len);
        memcpy(*pkcs7, buf, *pkcs7_len);
    }

    BIO_free_all(result);
    return EST_ERR_NONE;
}

/*
 * Callback function used by EST to generate a private key
 *
 * p_priv_key  contains a pointer to the key we will populate
 */
static int generate_private_key (EVP_PKEY **p_priv_key)
{
    EVP_PKEY *priv_key = NULL;
    RSA *rsa = NULL;
    BIGNUM *bn = NULL;
    BIO *out = NULL;
    int rv = EST_ERR_NONE;

    if (!p_priv_key) {
        rv = EST_ERR_INVALID_PARAMETERS;
        goto end;
    }

    rsa = RSA_new();
    if (!rsa) {
        rv = EST_ERR_MALLOC;
        printf("***ESTCLIENT [ERROR][generate_private_key]--> Failed to allocate RSA struct");
        goto end;
    }
    bn = BN_new();
    if (!bn) {
        rv = EST_ERR_MALLOC;
        printf("***ESTCLIENT [ERROR][generate_private_key]--> Failed to allocate BN struct");
        goto end;
    }

    BN_set_word(bn, 0x10001);

    RSA_generate_key_ex(rsa, 4096, bn, NULL);
    out = BIO_new(BIO_s_mem());
    PEM_write_bio_RSAPrivateKey(out,rsa,NULL,NULL,0,NULL,NULL);

    priv_key = PEM_read_bio_PrivateKey(out, NULL, NULL, NULL);
    if (priv_key == NULL) {
        rv = EST_ERR_PEM_READ;
        printf("Error while reading PEM encoded private key BIO: ");
        goto end;
    }
    *p_priv_key = priv_key;

    end:
    if (out) {
        BIO_free(out);
    }
    if (rsa) {
        RSA_free(rsa);
    }
    if (bn) {
        BN_free(bn);
    }
    return rv;
}

/*
 * Callback function used by EST stack to process a PKCS10
 * enrollment request with the CA.  The parameters are:
 *
 *   pkcs10     Contains the CSR that should be sent to
 *              the CA to be signed.
 *   pkcs10_len Length of the CSR char array
 *   pcks7      Should contain the signed PKCS7 certificate
 *              from the CA server.  You'll need allocate
 *              space and copy the cert into this char array.
 *   pkcs7_len  Length of the pkcs7 char array, you will set this.
 *   pkcs8      Should contain the signed PKCS8 key
 *              from the EST server context.  You'll need allocate
 *              space and copy the cert into this char array.
 *   pkcs8_len  Length of the pkcs8 char array, you will set this.
 *   user_id    If HTTP authentication was used to identify the
 *              EST client, this will contain the user ID supplied
 *              by the client.
 *   peer_cert  If the EST client presented a certificate to identify
 *              itself during the TLS handshake, this parameter will
 *              contain that certificate.
 *   path_seg   If the incoming request contains a path segment it
 *              is extracted from the URI and passed here.  Typically
 *              used to mux between multiple CAs or to identify a
 *              specific profile to use by the CA.
 *   app_data   an optional pointer to information that is to be
 *              used by the application layer.
 *
 */
static int process_srvr_side_keygen_pkcs10_enrollment (unsigned char * pkcs10, int p10_len,
                               unsigned char **pkcs7, int *pkcs7_len,
                               unsigned char **pkcs8, int *pkcs8_len,
                               char *user_id, X509 *peer_cert, char *path_seg,
                               void *app_data)
{
    BIO *result = NULL;
    char *buf;
#ifndef WIN32
    int rc;
#endif
    char sn[64];
    char file_name[MAX_FILENAME_LEN];

    if (verbose) {
        /*
         * Informational only
         */
        if (user_id) {
            /*
             * Should be safe to log the user ID here since HTTP auth
             * has succeeded at this point.
             */
            printf("\n%s - User ID is %s\n", __FUNCTION__, user_id);
        }
        if (peer_cert) {
            memset(sn, 0, 64);
            extract_sub_name(peer_cert, sn, 64);
            printf("\n%s - Peer cert CN is %s\n", __FUNCTION__, sn);
        }
        if (app_data) {
            printf("ex_data value is %x\n", *((unsigned int *) app_data));
        }
        if (path_seg) {
            printf("\nPath segment was included in enrollment URI. "
                "Path Segment = %s\n", path_seg);
        }
    }

    /*
     * If we're simulating manual certificate enrollment,
     * the CA will not automatically sign the cert request.
     * We'll attempt to lookup in our local table if this
     * cert has already been sent to us, if not, add it
     * to the table and send the 'retry' message back to the
     * client.  But if this cert request has been seen in the
     * past, then we'll continue with the enrollment.
     * To summarize, we're simulating manual enrollment by
     * forcing the client to request twice, and we'll automatically
     * enroll on the second request.
     */
    if (manual_enroll) {
        if (lookup_pkcs10_request(pkcs10, p10_len)) {
            /*
             * We've seen this cert request in the past.
             * Remove it from the lookup table and allow
             * the enrollment to continue.
             * Fall-thru to enrollment logic below
             */
        } else {
            /*
             * Couldn't find this request, it's the first time
             * we've seen it.  Therefore, send the retry
             * response.
             */
            return (EST_ERR_CA_ENROLL_RETRY);
        }

    }

#ifndef WIN32
    rc = pthread_mutex_lock(&m);
    if (rc) {
        printf("\nmutex lock failed rc=%d", rc);
        exit(1);
    }
#else
    EnterCriticalSection(&enrollment_critical_section);
#endif

    if (write_csr) {
        /*
         * Dump out pkcs10 to a file, this will contain a list of the OIDs in the CSR.
         */
        snprintf(file_name, MAX_FILENAME_LEN, "/tmp/csr.p10");
        write_binary_file(file_name, pkcs10, p10_len);
    }

    result = ossl_simple_enroll(pkcs10, p10_len);
#ifndef WIN32
    rc = pthread_mutex_unlock(&m);
    if (rc) {
        printf("\nmutex unlock failed rc=%d", rc);
        exit(1);
    }
#else
    LeaveCriticalSection(&enrollment_critical_section);
#endif

    /*
     * The result is a BIO containing the pkcs7 signed certificate
     * Need to convert it to char and copy the results so we can
     * free the BIO.
     */
    *pkcs7_len = BIO_get_mem_data(result, (char**) &buf);
    if (*pkcs7_len > 0 && *pkcs7_len < MAX_CERT_LEN) {
        *pkcs7 = malloc(*pkcs7_len);
        memcpy(*pkcs7, buf, *pkcs7_len);
    }

    BIO_free_all(result);
    return EST_ERR_NONE;
}

//The following is a default CSR attributes response that also
//contains challengePassword
#define TEST_CSR "MCYGBysGAQEBARYGCSqGSIb3DQEJAQYFK4EEACIGCWCGSAFlAwQCAg=="
unsigned char * process_csrattrs_request (int *csr_len, char *path_seg, X509 *peer_cert,
                                          void *app_data)
{
    unsigned char *csr_data;
    char *t = NULL;
    int t_len;

    if (path_seg) {
        printf("\nPath segment was included in csrattrs URI. "
            "Path Segment = %s\n", path_seg);
    }

    t = getenv("EST_CSR_ATTR");
    if (t) {
        t_len = strlen(t);
        csr_data = malloc(t_len + 1);
        strncpy((char *) csr_data, t, t_len);
        *csr_len = t_len;
    } else {
        *csr_len = sizeof(TEST_CSR);
        csr_data = malloc(*csr_len + 1);
        strcpy((char *) csr_data, TEST_CSR);
    }
    return (csr_data);
}

#if ENABLE_BRSKI
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
    if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
        strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
        return 0;
    }
    return -1;
}


static int dump(const char *js, jsmntok_t *t, size_t count, int indent) {
        int i, j, k;
        if (count == 0) {
                return 0;
        }
        if (t->type == JSMN_PRIMITIVE) {
                printf("%.*s", t->end - t->start, js+t->start);
                return 1;
        } else if (t->type == JSMN_STRING) {
                printf("'%.*s'", t->end - t->start, js+t->start);
                return 1;
        } else if (t->type == JSMN_OBJECT) {
                printf("\n");
                j = 0;
                for (i = 0; i < t->size; i++) {
                        for (k = 0; k < indent; k++) printf("  ");
                        j += dump(js, t+1+j, count-j, indent+1);
                        printf(": ");
                        j += dump(js, t+1+j, count-j, indent+1);
                        printf("\n");
                }
                return j+1;
        } else if (t->type == JSMN_ARRAY) {
                j = 0;
                printf("\n");
                for (i = 0; i < t->size; i++) {
                        for (k = 0; k < indent-1; k++) printf("  ");
                        printf("   - ");
                        j += dump(js, t+1+j, count-j, indent+1);
                        printf("\n");
                }
                return j+1;
        }
        return 0;
}

/*   { */
/*      "ietf-voucher:voucher": { */
/*        "nonce": "62a2e7693d82fcda2624de58fb6722e5", */
/*        "assertion": "logging" */
/*        "pinned-domain-cert": "<base64 encoded certificate>" */
/*        "serial-number": "JADA123456789" */
/*      } */
/*    } */
#define BRSKI_DEVICE_SERIAL_NUM "F7BE0D"
#define VOUCHER "{\n\r\"ietf-voucher:voucher\":{\n\r\"nonce\":\"%s\",\n\r\"assertion\":\"logging\",\n\r\"pinned-domain-cert\":\"%s\",\n\r\"serial-number\":\"%s\"}\n\r}"

/*
 * Callback function used by EST stack to process a BRSK 
 * voucher request.  The parameters are:
 *
 *   voucher_req Contains the voucher request from the client
 *   voucher_req_len Length of the voucher request
 *   voucher     Pointer to a buffer pointer that will contain
 *               the voucher to be returned
 *   voucher_len Pointer to an integer that will be set to the length
 *               of the returned voucher.
 *   peer_cert - client certificate, if available, in internal X509
 *               structure format
 */

EST_BRSKI_CALLBACK_RC
process_brski_voucher_request (char *voucher_req, int voucher_req_len,
                               char **voucher, int *voucher_len, X509 *peer_cert)
{
    char *voucher_buf = NULL;
    jsmn_parser p;
    jsmntok_t *tok;
    size_t tokcount = 100;
    int parser_resp;
    int i;
    int nonce_found = 0;
    int incoming_server_cert_found = 0;
    char incoming_nonce[EST_BRSKI_VOUCHER_REQ_NONCE_SIZE+1];
    char incoming_server_cert[EST_BRSKI_MAX_CACERT_LEN+1];
    char *ser_num_str = NULL;

    char *signed_voucher_buf;
    BUF_MEM *buf_mem_ptr;

    BIO *voucher_bio = NULL;
#ifdef CMS_SIGNING
    BIO *cms_bio_out = NULL;
    CMS_ContentInfo *voucher_cms = NULL;
#else
    PKCS7 *voucher_p7 = NULL;
    BIO *out = NULL;
#ifdef BASE64_ENCODE_VOUCHERS
    BIO *b64 = NULL;
#endif /* BASE64_ENCODE_VOUCHERS */
#endif            
    int rc;
    EST_BRSKI_CALLBACK_RC rv =  EST_BRSKI_CB_FAILURE; 
    
    memset(incoming_nonce, 0, EST_BRSKI_VOUCHER_REQ_NONCE_SIZE+1);
    memset(incoming_server_cert, 0, EST_BRSKI_MAX_CACERT_LEN+1);

    printf("BRSKI voucher request received\n");
    printf(" voucher_req = %s\n voucher_req_len = %d\n",
           voucher_req, voucher_req_len);

    /*
     * Parse the voucher request and obtain the nonce
     */
    jsmn_init(&p);
    tok = calloc(tokcount, sizeof(*tok));
    if (tok == NULL) {
        printf("calloc(): errno=%d\n", errno);
        return 3;
    }
    parser_resp = jsmn_parse(&p, (char *)voucher_req, (size_t)voucher_req_len,
                             tok, tokcount);
    if (parser_resp < 0) {
        printf("Voucher request parse failed. parse error = %d\n", parser_resp);
    } else {
        dump((char *)voucher_req, tok, p.toknext, 0);
        printf("Voucher request parsed\n");
    }
    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(voucher_req, &tok[i], "nonce") == 0) {
            sprintf(incoming_nonce, "%.*s", tok[i+1].end-tok[i+1].start,
                    voucher_req + tok[i+1].start);            
            printf("Found nonce %s\n", incoming_nonce);
            nonce_found = 1;
            break;
        }
    }
    if (!nonce_found) {
        printf("Nonce missing from voucher request\n");
        free(tok);
        return (EST_BRSKI_CB_FAILURE);
    }
    
    /*
     * Now look for the Registrar's cert
     */
    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(voucher_req, &tok[i], "proximity-registrar-cert") == 0) {
            sprintf(incoming_server_cert, "%.*s", tok[i+1].end-tok[i+1].start,
                    voucher_req + tok[i+1].start);            
            printf("Found proximity registrar cert %s\n", incoming_server_cert);
            incoming_server_cert_found = 1;
            break;
        }
    }
    free(tok);
    tok = NULL;
    if (!incoming_server_cert_found) {
        printf("Proximity registrar cert missing from voucher request\n");
        return (EST_BRSKI_CB_FAILURE);
    }

    /*
     * Obtain the serial number of the pledge device from its ID cert
     */
    ser_num_str = est_find_ser_num_in_subj(peer_cert);
    if (ser_num_str == NULL) {
        char *subj;

        printf("Pledge MFG cert does not contain a serial number.");

        subj = X509_NAME_oneline(X509_get_subject_name(peer_cert), NULL, 0);
        printf("Client MFG cert subject: %s", subj);
        OPENSSL_free(subj);
        
        return (EST_ERR_CLIENT_BRSKI_SERIAL_NUM_MISSING);        
    }
    
    voucher_buf = calloc(EST_BRSKI_MAX_VOUCHER_LEN, sizeof(char));
    if (voucher_buf) {
        *voucher_len = snprintf(voucher_buf, EST_BRSKI_MAX_VOUCHER_LEN, VOUCHER,
                                incoming_nonce, brski_ca_certs, ser_num_str);
        *voucher = voucher_buf;
        printf("Voucher to be returned = %s\n", *voucher);
    } else {
        *voucher = NULL;
        *voucher_len = 0;
        rv = (EST_BRSKI_CB_FAILURE);
        goto end;
    }

    /*
     * If the MASA CA has been enabled then sign the voucher with the MASA's
     * signing key
     */
    if (masa_ca_enabled) {

        signed_voucher_buf = calloc(EST_BRSKI_MAX_VOUCHER_LEN, sizeof(char));
        if (signed_voucher_buf == NULL) {
            printf("calloc(): errno=%d\n", errno);
            *voucher = NULL;
            *voucher_len = 0;
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
            
        voucher_bio = BIO_new_mem_buf(voucher_buf, *voucher_len);
        if (voucher_bio == NULL) {
            printf("Unable to assign voucher to BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#ifdef CMS_SIGNING
        voucher_cms = CMS_sign(masa_ca_root, masa_ca_priv_key, NULL, voucher_bio,
                               (CMS_BINARY|CMS_NOSMIMECAP));
        if (voucher_cms == NULL) {
            printf("Unable to sign voucher");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#else       
        voucher_p7 = PKCS7_sign(masa_ca_root, masa_ca_priv_key, NULL, voucher_bio,
                                (PKCS7_BINARY|PKCS7_NOSMIMECAP));
        if (voucher_p7 == NULL) {
            printf("Unable to sign voucher");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#endif

/*
 * For now, this is not going to be set.  It eventually needs to be set to the
 * OID assigned to represent "JSON-encoded voucher" per voucher profile 06,
 * "An eContentType of TBD1 indicates the content is a JSON- encoded voucher."
 */
#if 0
        /* Set inner content type to signed PKCS7 receipt */
        /* PDB NOTE: probably not needed.  It appears that it's already set to
         * this contentType */
        if (!CMS_set1_eContentType(voucher_p7, OBJ_nid2obj(NID_pkcs7_signed))) {
            printf("Unable to assign ContentType to CMS structure");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#endif
#ifdef CMS_SIGNING
        /*
         * convert it into something that can be sent in the voucher
         * response
         */
        cms_bio_out = BIO_new(BIO_s_mem());
        if (cms_bio_out == NULL) {
            printf("Unable to create output BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
            
        rc = PEM_write_bio_CMS(cms_bio_out, voucher_cms);
            
        if (rc == 0) {
            printf("Unable to assign voucher to output BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }                

        /* char buf[1024*20]; */
            
        /* memset(&buf[0], 0, 1024*20); */
        BIO_get_mem_ptr(cms_bio_out, &buf_mem_ptr);
        memcpy(signed_voucher_buf, buf_mem_ptr->data, buf_mem_ptr->length);

/*             len = BIO_get_mem_data(cms_bio_out, (char**) &buf); */
/*             printf("%d\n", len); */
            
/*             rc = PEM_write_bio_CMS_stream(cms_bio_out, voucher_cms,  ); */
/*             if (!rc) { */
/*                 printf("Error in PEM_write_bio_PKCS7"); */
/*                 ossl_dump_ssl_errors(); */
/*                 return (EST_BRSKI_CB_FAILURE); */
/*             }             */

        *voucher = signed_voucher_buf;
        *voucher_len = buf_mem_ptr->length;
        rv = EST_BRSKI_CB_SUCCESS;
#else  /* PKCS7 signing */
#ifdef BASE64_ENCODE_VOUCHERS        
        b64 = BIO_new(BIO_f_base64());
        if (!b64) {
            printf("BIO_new failed for b64 output BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#endif /* BASE64_ENCODE_VOUCHERS */
        out = BIO_new(BIO_s_mem());
        if (!out) {
            printf("BIO_new failed for output BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
#ifdef BASE64_ENCODE_VOUCHERS        
        out = BIO_push(b64, out);
#endif /* BASE64_ENCODE_VOUCHERS */
        rc = i2d_PKCS7_bio(out, voucher_p7);
        (void)BIO_flush(out);
        if (!rc) {
            printf("Unable to assign voucher to output BIO");
            ossl_dump_ssl_errors();
            *voucher = NULL;
            *voucher_len = 0;
            free(signed_voucher_buf);
            rv = (EST_BRSKI_CB_FAILURE);
            goto end_masa_ca;
        }
            
        BIO_get_mem_ptr(out, &buf_mem_ptr);
        memcpy(signed_voucher_buf, buf_mem_ptr->data, buf_mem_ptr->length);

        /*
         * Return the signed voucher
         */
        *voucher = signed_voucher_buf;
        *voucher_len = buf_mem_ptr->length;
        rv = EST_BRSKI_CB_SUCCESS;
        
#endif
        end_masa_ca:
        if(voucher_bio) {
            BIO_free_all(voucher_bio);
        }
        if (voucher_buf) {
            free(voucher_buf);
        }
    }
    end:
#ifdef CMS_SIGNING
    if (voucher_cms) {
        CMS_ContentInfo_free(voucher_cms)
    }
    if(cms_bio_out){
        BIO_free(cms_bio_out);
    }
#else
    if(voucher_p7) {
        PKCS7_free(voucher_p7);
    }
    if(out){
        BIO_free_all(out);
    }
#endif          
    return rv;    
}


/*
 * Callback function used by EST stack to process a BRSK 
 * voucher status indication.  The parameters are:
 *
 *   voucher_status Pointer buffer containing the voucher status
 *   voucher_status_len Integer containing the length of the voucher_status buffer
 *   peer_cert certificate of the client used in the TLS connection.
 *
 */
static
EST_BRSKI_CALLBACK_RC
process_brski_voucher_status (char *voucher_status, int voucher_status_len, X509 *peer_cert)
{
    jsmn_parser p;
    jsmntok_t *tok;
    size_t tokcount = 100;
    int parser_resp;
    int i;
    int status_found = 0;
    char incoming_status[5+1];
    int reason_found = 0;
    char incoming_reason[EST_BRSKI_MAX_REASON_LEN];
    
    memset(incoming_status, 0, 5+1);

    printf("BRSKI voucher status received\n");
    printf(" voucher_status = %s\n voucher_status_len = %d\n",
           voucher_status, voucher_status_len);
    
    /*
     * Parse the voucher response and obtain the status and reason
     */
    jsmn_init(&p);
    tok = calloc(tokcount, sizeof(*tok));
    if (tok == NULL) {
        printf("calloc(): errno=%d\n", errno);
        return 3;
    }
    parser_resp = jsmn_parse(&p, (char *)voucher_status, (size_t)voucher_status_len,
                             tok, tokcount);
    if (parser_resp < 0) {
        printf("Voucher response parse failed. parse error = %d\n", parser_resp);
    } else {
        dump((char *)voucher_status, tok, p.toknext, 0);
        printf("Voucher status parsed\n");
    }
    
    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(voucher_status, &tok[i], "Status") == 0) {
            sprintf(incoming_status, "%.*s", tok[i+1].end-tok[i+1].start,
                    voucher_status + tok[i+1].start);            
            printf("Found status %s\n", incoming_status);
            status_found = 1;
            break;
        }
    }
    if (!status_found) {
        printf("Status value missing from voucher status\n");
        free(tok);
        return (EST_BRSKI_CB_FAILURE);
    }

    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(voucher_status, &tok[i], "Reason") == 0) {
            sprintf(incoming_reason, "%.*s", tok[i+1].end-tok[i+1].start,
                    voucher_status + tok[i+1].start);            
            printf("Found reason %s\n", incoming_reason);
            reason_found = 1;
            break;
        }
    }
    free(tok);
    tok = NULL;
    if (!reason_found) {
        printf("Reason value missing from voucher status\n");
        return (EST_BRSKI_CB_FAILURE);
    }
    
    return EST_BRSKI_CB_SUCCESS;
}


/*
 * Callback function used by EST stack to process a BRSK 
 * enrollment status.  The parameters are:
 *
 *   enroll_status Pointer buffer containing the voucher status
 *   enroll_status_len Integer containing the length of the voucher_status buffer
 *   peer_cert certificate of the client used in the TLS connection.
 */
EST_BRSKI_CALLBACK_RC
process_brski_enroll_status (char *enroll_status, int enroll_status_len, X509 *peer_cert)
{
    jsmn_parser p;
    jsmntok_t *tok;
    size_t tokcount = 100;
    int parser_resp;
    int i;
    int status_found = 0;
    char incoming_status[5+1];
    int reason_found = 0;
    char incoming_reason[EST_BRSKI_MAX_REASON_LEN];
    
    memset(incoming_status, 0, 5+1);

    printf("BRSKI enroll status received\n");
    printf(" enroll_status = %s\n enroll_status_len = %d\n",
           enroll_status, enroll_status_len);

    /*
     * Parse the voucher response and obtain the status and reason
     */
    jsmn_init(&p);
    tok = calloc(tokcount, sizeof(*tok));
    if (tok == NULL) {
        printf("calloc(): errno=%d\n", errno);
        return 3;
    }
    parser_resp = jsmn_parse(&p, (char *)enroll_status, (size_t)enroll_status_len,
                             tok, tokcount);
    if (parser_resp < 0) {
        printf("Enroll response parse failed. parse error = %d\n", parser_resp);
    } else {
        dump((char *)enroll_status, tok, p.toknext, 0);
        printf("Enroll status parsed\n");
    }
    
    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(enroll_status, &tok[i], "Status") == 0) {
            sprintf(incoming_status, "%.*s", tok[i+1].end-tok[i+1].start,
                    enroll_status + tok[i+1].start);            
            printf("Found status %s\n", incoming_status);
            status_found = 1;
            break;
        }
    }
    if (!status_found) {
        printf("Status value missing from enroll status\n");
        free(tok);
        return (EST_BRSKI_CB_FAILURE);
    }

    for (i = 1; i < parser_resp; i++) {
        if (jsoneq(enroll_status, &tok[i], "Reason") == 0) {
            sprintf(incoming_reason, "%.*s", tok[i+1].end-tok[i+1].start,
                    enroll_status + tok[i+1].start);            
            printf("Found reason: %s\n", incoming_reason);
            reason_found = 1;
            break;
        }
    }
    free(tok);
    tok = NULL;
    if (!reason_found) {
        printf("Reason value missing from enroll status\n");
        return (EST_BRSKI_CB_FAILURE);
    }

    return EST_BRSKI_CB_SUCCESS;    
}

/*
 * This function is used to read the CERTS in a BIO and build a
 * stack of X509* pointers.  This is used during the PEM to
 * PKCS7 conversion process.
 */
static int est_add_certs_from_BIO (STACK_OF(X509) *stack, BIO *in)
{
    int count = 0;
    int ret = -1;

    STACK_OF(X509_INFO) * sk = NULL;
    X509_INFO *xi;


    /* This loads from a file, a stack of x509/crl/pkey sets */
    sk = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
    if (sk == NULL) {
        printf("Unable to read certs from PEM encoded data");
        return (ret);
    }

    /* scan over it and pull out the CRL's */
    while (sk_X509_INFO_num(sk)) {
        xi = sk_X509_INFO_shift(sk);
        if (xi->x509 != NULL) {
            sk_X509_push(stack, xi->x509);
            xi->x509 = NULL;
            count++;
        }
        X509_INFO_free(xi);
    }

    ret = count;

    /* never need to OPENSSL_free x */
    if (sk != NULL) {
        sk_X509_INFO_free(sk);
    }
    return (ret);
}


/*
 * Converts from PEM to pkcs7 encoded certs.  Optionally
 * applies base64 encoding to the output.  This is used
 * when creating the cached cacerts response.  The returned
 * BIO contains the PKCS7 encoded certs.  The response
 * can optionally be base64 encoded by passing in a
 * non-zero value for the do_base_64 argument.  The caller
 * of this function should invoke BIO_free_all() on the
 * return value to avoid memory leaks.  Note, BIO_free() 
 * will not be sufficient.
 */
static
BIO * est_get_certs_pkcs7 (BIO *in, int do_base_64)
{
    STACK_OF(X509) * cert_stack = NULL;
    PKCS7_SIGNED *p7s = NULL;
    PKCS7 *p7 = NULL;
    BIO *out = NULL;
    BIO *b64;
    int buflen = 0;

    /*
     * Create a PKCS7 object 
     */
    if ((p7 = PKCS7_new()) == NULL) {
        printf("pkcs7_new failed");
        goto cleanup;
    }
    /*
     * Create the PKCS7 signed object
     */
    if ((p7s = PKCS7_SIGNED_new()) == NULL) {
        printf("pkcs7_signed_new failed");
        goto cleanup;
    }
    /*
     * Set the version
     */
    if (!ASN1_INTEGER_set(p7s->version, 1)) {
        printf("ASN1_integer_set failed");
        goto cleanup;
    }

    /*
     * Create a stack of X509 certs
     */
    if ((cert_stack = sk_X509_new_null()) == NULL) {
        printf("stack malloc failed");
        goto cleanup;
    }

    /*
     * Populate the cert stack
     */
    if (est_add_certs_from_BIO(cert_stack, in) < 0) {
        printf("Unable to load certificates");
        ossl_dump_ssl_errors();
        goto cleanup;
    }

    /*
     * Create the BIO which will receive the output
     */
    out = BIO_new(BIO_s_mem());
    if (!out) {
        printf("BIO_new failed");
        goto cleanup;
    }

    /*
     * Add the base64 encoder if needed
     */
    if (do_base_64) {
        b64 = BIO_new(BIO_f_base64());
        if (b64 == NULL) {
            printf("BIO_new failed while attempting to create base64 BIO");
            ossl_dump_ssl_errors();
            goto cleanup;
        }    
        out = BIO_push(b64, out);
    }

    p7->type = OBJ_nid2obj(NID_pkcs7_signed);
    p7->d.sign = p7s;
    p7s->contents->type = OBJ_nid2obj(NID_pkcs7_data);
    p7s->cert = cert_stack;

    /*
     * Convert from PEM to PKCS7
     */
    buflen = i2d_PKCS7_bio(out, p7);
    if (!buflen) {
        printf("PEM_write_bio_PKCS7 failed");
        ossl_dump_ssl_errors();
        BIO_free_all(out);
        out = NULL;
        goto cleanup;
    }
    (void)BIO_flush(out);

cleanup:
    /* 
     * Only need to cleanup p7.  This frees up the p7s and
     * cert_stack allocations for us since these are linked
     * to the p7.
     */
    if (p7) {
        PKCS7_free(p7);
    }

    return out;
}

static
EST_ERROR est_load_ca_certs (unsigned char *raw, int size)
{
    BIO *cacerts = NULL;
    BIO *in;
    unsigned char *retval;
    
    in = BIO_new_mem_buf(raw, size);
    if (in == NULL) {
        printf("Unable to open the raw cert buffer");
        return (EST_ERR_LOAD_CACERTS);
    }

    /*
     * convert the CA certs to PKCS7 encoded char array
     * This is used by an EST server to respond to the
     * cacerts request.
     */
    cacerts = est_get_certs_pkcs7(in, 1);
    if (!cacerts) {
        printf("est_get_certs_pkcs7 failed");
        BIO_free(in);
        return (EST_ERR_LOAD_CACERTS);
    }
    
    brski_ca_certs_len = (int) BIO_get_mem_data(cacerts, (char**)&retval);
    if (brski_ca_certs_len <= 0) {
        printf("Failed to copy PKCS7 data");
        BIO_free_all(cacerts);
        BIO_free(in);
        return (EST_ERR_LOAD_CACERTS);
    }
    
    brski_ca_certs = calloc(brski_ca_certs_len, sizeof(char));
    if (!brski_ca_certs) {
        printf("calloc failed");
        BIO_free_all(cacerts);
        BIO_free(in);
        return (EST_ERR_LOAD_CACERTS);
    }
    memcpy(brski_ca_certs, retval, brski_ca_certs_len);
    BIO_free_all(cacerts);
    BIO_free(in);
    return (EST_ERR_NONE);
}

/*
 * Used to set up the MASA credentials to be used to sign vouchers
 */
int set_brski_masa_credentials (char *masa_root_ca_file, char *masa_priv_key_file)
{
    BIO *certin;    
    
    /*
     * read in the MASA CA root cert
     */
    if (masa_root_ca_file[0]) {
        certin = BIO_new(BIO_s_file());
        if (certin == NULL) {
            printf("Unable to create BIO");
            return (-1);
        }
        if (BIO_read_filename(certin, masa_root_ca_file) <= 0) {
            printf("\nUnable to read MASA root CA certificate file %s\n",
                   masa_root_ca_file);
            return (-1);
        }
        /*
         * Read the file. Expected to be PEM encoded. 
         */
        masa_ca_root = PEM_read_bio_X509(certin, NULL, NULL, NULL);
        if (masa_ca_root == NULL) {
            printf("\nError while reading PEM encoded MASA CA Root certificate file %s\n",
                   masa_root_ca_file);
                return (-1);
        }
        BIO_free(certin);
    }
    
    /*
     * Read in the matching MASA private key
     */
    if (masa_priv_key_file[0]) {
        masa_ca_priv_key = read_private_key(masa_priv_key_file, PEM_def_callback);
        if (masa_ca_priv_key == NULL) {
            printf("\nError while reading PEM encoded MASA CA private key file %s\n",
                   masa_priv_key_file);
            return (-1);
        }
    }
    
    masa_ca_enabled = 1;
    
    return (0);
}
#endif

static char digest_user[3][34] = { "estuser", "estrealm", ""};

/*
 * This callback is invoked by libEST when performing
 * HTTP authentication of the EST client.  libEST will
 * parse the auth credentials from the HTTP header.  We
 * must validate the user ourselves since libEST does
 * not maintain a user database.  This allows us to hook
 * into a Radius server, or some other external user
 * database.
 *
 * For this example code, we simply have a local hard-coded
 * user database.
 *
 * Return 1 to signal the user is valid, 0 to fail the auth
 */
int process_http_auth (EST_CTX *ctx, EST_HTTP_AUTH_HDR *ah, X509 *peer_cert,
                       char *path_seg, void *app_data)
{
    int user_valid = 0;
    char *digest;
    char *user = "estuser";
    char *pass = "estpwd";

    if (path_seg) {
        printf("\nPath segment was included in authenticate URI. "
               "Path Segment = %s\n", path_seg);
    }

    switch (ah->mode) {
    case AUTH_BASIC:
        /*
         * this is where we might hook into a Radius server
         * or some external database to authenticate a
         * userID/password.  But for this example code,
         * we just hard-code a local user for testing
         * the libEST API.
         */
        if (enable_enhcd_cert_auth) {
            user = "/CN=127.0.0.1";
            pass = set_cert_auth_ah_pwd ? cert_auth_ah_pwd : DEFAULT_ENHCD_CERT_PWD;
        }
        if (!strcmp(ah->user, user) && !strcmp(ah->pwd, pass)) {
            /* The user is valid */
            user_valid = 1;
        }
        break;
    case AUTH_DIGEST:
        /*
         * Check the user's name
         */
        if (strcmp(ah->user, digest_user[0])) {
            return 0;
        }

        /*
         * Call the helper function to calculate the digest,
         * This is then compared against the digest provided
         * by the client.
         */
        digest = est_server_generate_auth_digest(ah, digest_user[2]);
        if (!strncmp(digest, ah->response, 32)) {
            user_valid = 1;
        }
        free(digest);
        break;
    case AUTH_TOKEN:
        /*
         * The bearer token has just been passed up from the EST Server
         * library.  Assuming it's an OAuth 2.0 based access token, it would
         * now be sent along to the OAuth Authorization Server.  The
         * Authorization Server would return either a success or failure
         * response.
         */
        printf("\nConfigured for HTTP Token Authentication\n");
        printf(
            "Configured access token = %s \nClient access token received = %s\n\n",
            ah->auth_token, valid_token_value);

        if (!strcmp(ah->auth_token, valid_token_value)) {
            /* The token is currently valid */
            user_valid = 1;
        }
        break;
    case AUTH_FAIL:
    case AUTH_NONE:
    default:
        return 0;
        break;
    }
    return user_valid;
}

/*
 * This callback is issued during the TLS-SRP handshake.
 * We can use this to get the userid from the TLS-SRP handshake.
 * If a verifier file as provided, we must pull the SRP verifier
 * parameters and invoke SSL_set_srp_server_param() with these
 * values to allow the TLS handshake to succeed.  If the application
 * layer wants to use their own verifier store, they would
 * hook into it here.  They would lookup the verifier parameters
 * based on the userid and return those parameters by invoking
 * SSL_set_srp_server_param().
 */
static int process_ssl_srp_auth (SSL *s, int *ad, void *arg)
{

    char *login = SSL_get_srp_username(s);
    SRP_user_pwd *user;

    if (!login)
        return (-1);

    user = SRP_VBASE_get1_by_user(srp_db, login);

    if (user == NULL) {
        printf("User doesn't exist in SRP database\n");
        return SSL3_AL_FATAL;
    }

    /*
     * Get the SRP parameters for the user from the verifier database.
     * Provide these parameters to TLS to complete the handshake
     */
    if (SSL_set_srp_server_param(s, user->N, user->g, user->s, user->v,
                                 user->info) < 0) {
        *ad = SSL_AD_INTERNAL_ERROR;
        return SSL3_AL_FATAL;
    }

    printf("SRP parameters set: username = \"%s\" info=\"%s\" \n", login,
           user->info);

    SRP_user_pwd_free(user);
    user = NULL;
    login = NULL;
    fflush(stdout);
    return SSL_ERROR_NONE;
}

#ifdef HAVE_OLD_OPENSSL
/*
 * We're using OpenSSL, both as the CA and libest
 * requires it.  OpenSSL requires these platform specific
 * locking callbacks to be set when multi-threaded support
 * is needed.
 */
static MUTEX_TYPE *mutex_buf = NULL;
static void locking_function (int mode, int n, const char * file, int line)
{
    if (mode & CRYPTO_LOCK)
        MUTEX_LOCK(mutex_buf[n]);
    else
        MUTEX_UNLOCK(mutex_buf[n]);
}

static unsigned long id_function (void)
{
    return ((unsigned long) THREAD_ID);
}
#endif

/*
 * This routine destroys the EST context and frees
 * up other resources to prevent a memory leak.
 */
void cleanup (void)
{
#ifdef HAVE_OLD_OPENSSL    
    int i;
#endif
    
    est_server_stop(ectx);
    est_destroy(ectx);

    if (srp_db) {
        SRP_VBASE_free(srp_db);
    }

#ifdef HAVE_OLD_OPENSSL    
    /*
     * Tear down the mutexes used by OpenSSL
     */
    if (!mutex_buf)
        return;
    CRYPTO_set_id_callback(NULL);
    CRYPTO_set_locking_callback(NULL);
    for (i = 0; i < CRYPTO_num_locks(); i++)
        MUTEX_CLEANUP(mutex_buf[i]);
    free(mutex_buf);
    mutex_buf = NULL;
#endif
    
    BIO_free(bio_err);
    free(cacerts_raw);
    free(trustcerts);
    free(enhcd_cert_truststore);
    est_apps_shutdown();
#ifndef WIN32
    pthread_mutex_destroy(&m);
#else
    DeleteCriticalSection(&enrollment_critical_section);
#endif
}


/*
 * This is the main entry point into the example EST server.
 * This routine parses the command line options, reads in the
 * cert chains, creates an EST context, initializes the HTTP
 * layer (mongoose), and starts a simple TCP server to accept
 * incoming EST requests.
 */
int main (int argc, char **argv)
{
    char c;
#ifdef HAVE_OLD_OPENSSL    
    int i;
#endif    
#if ENABLE_BRSKI
    int rc;
#endif    

    X509 *x;
    EVP_PKEY * priv_key;
    BIO *certin;
    DH *dh;
    EST_ERROR rv;
    int sleep_delay = 0;
    int retry_period = 300;
    char vfile[255];
    int option_index = 0;
    pem_password_cb *priv_key_cb = NULL;
    int nid;
#ifdef HAVE_LIBCOAP
    int coap_rc;
#endif
    static struct option long_options[] = {
        {"srp", 1, NULL, 0},
        {"enforce-csr", 0, NULL, 0},
        {"token", 1, 0, 0},
#if ENABLE_BRSKI
        {"enable-brski", 0, 0, 0},
        {"masa-root-ca", 1, 0, 0 },
        {"masa-priv-key", 1, 0, 0 },
#endif
        {"keypass", 1, 0, 0},
        {"keypass_stdin", 1, 0, 0 },
        {"keypass_arg", 1, 0, 0 },
#ifdef HAVE_LIBCOAP
        {"enable-coap", 0, 0, 0},
        {"dtls-handshake-timeout", 1, 0, 0},
        {"dtls-handshake-mtu", 1, 0, 0},
        {"dtls-session-max", 1, 0, 0},
#endif        
        {"enhcd_cert_auth", 0, 0, 0},
        {"cert_auth_ah_pwd", 1, 0, 0},
        {"cert_auth_csr_check_on", 0, 0, 0},
        {"enhcd_cert_local_nid", 1, 0, 0},
        {"enhcd_cert_mfg_name", 1, 0, 0},
        {"enhcd_cert_mfg_truststore", 1, 0, 0},
        {"enhcd_cert_mfg_nid", 1, 0, 0},
        {"perf-timers-on", 0, 0, 0},
        {NULL, 0, NULL, 0}
    };

#ifdef WIN32
    InitializeCriticalSection(&enrollment_critical_section);
#endif

    /* Show usage if -h or --help options are specified */
    if ((argc == 1)
        || (argc == 2
            && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))) {
        show_usage_and_exit();
    }
#if ENABLE_BRSKI
    memset(masa_root_ca_file, 0, EST_MAX_FILE_LEN+1);
    memset(masa_priv_key_file, 0, EST_MAX_FILE_LEN+1);
#endif
    while ((c = getopt_long(argc, argv, "?fhbwnovr:c:k:m:p:d:lt6", long_options,
                            &option_index)) != -1) {
        switch (c) {
        case 0:
#if 0
            printf("option %s", long_options[option_index].name);
            if (optarg) {
                printf(" with arg %s", optarg);
            }
            printf("\n");
#endif
            if (!strncmp(long_options[option_index].name, "srp",
                         strlen("srp"))) {
                srp = 1;
                strncpy(vfile, optarg, 255);
            }
            if (!strncmp(long_options[option_index].name, "enforce-csr",
                         strlen("enforce-csr"))) {
                enforce_csr = 1;
            }
            if (!strncmp(long_options[option_index].name, "token",
                         strlen("token"))) {
                http_token_auth = 1;
                memset(valid_token_value, 0, MAX_AUTH_TOKEN_LEN + 1);
                strncpy(&(valid_token_value[0]), optarg, MAX_AUTH_TOKEN_LEN);
            }
            if (!strncmp(long_options[option_index].name,"keypass_stdin", strlen("keypass_stdin"))) {
                priv_key_cb = PEM_def_callback;
            }
            if (!strncmp(long_options[option_index].name,"keypass_arg", strlen("keypass_arg"))) {
                strncpy(priv_key_pwd, optarg, MAX_PWD_LEN);
                priv_key_cb = string_password_cb;
            }
            if (!strncmp(long_options[option_index].name,"enhcd_cert_auth",
                         strlen("enhcd_cert_auth"))) {
                enable_enhcd_cert_auth = 1;
            }
            if (!strncmp(long_options[option_index].name,"cert_auth_ah_pwd",
                         strlen("cert_auth_ah_pwd"))) {
                strncpy(cert_auth_ah_pwd, optarg, MAX_PWD_LEN + 1);
                set_cert_auth_ah_pwd = 1;
            }
            if (!strncmp(long_options[option_index].name,"cert_auth_csr_check_on",
                         strlen("cert_auth_csr_check_on"))) {
                enhcd_cert_csr_check_on = ECA_CSR_CHECK_ON;
            }
            if (!strncmp(long_options[option_index].name,"enhcd_cert_local_nid",
                         strlen("enhcd_cert_local_nid"))) {
                strncpy(local_nid, optarg, MAX_PWD_LEN + 1);
                set_cert_auth_local_nid = 1;
            }
            if (!strncmp(long_options[option_index].name,"enhcd_cert_mfg_name",
                         strlen("enhcd_cert_mfg_name"))) {
                strncpy(mfg_name, optarg, MFG_NAME_MAX_LEN + 1);
                set_cert_auth_mfg_name = 1;
            }
            if (!strncmp(long_options[option_index].name,"enhcd_cert_mfg_truststore",
                         strlen("enhcd_cert_mfg_truststore"))) {
                strncpy(mfg_truststore_file, optarg, EST_MAX_FILE_LEN);
                set_enhcd_cert_truststore = 1;
            }
            if (!strncmp(long_options[option_index].name,"enhcd_cert_mfg_nid",
                         strlen("enhcd_cert_mfg_nid"))) {
                strncpy(mfg_nid, optarg, MAX_PWD_LEN + 1);
                set_cert_auth_mfg_nid = 1;
            }
#if ENABLE_BRSKI
            if (!strncmp(long_options[option_index].name, "enable-brski",
                         strlen("enable-brski"))) {
                brski_mode = 1;
            }
            if (!strncmp(long_options[option_index].name,"masa-root-ca", strlen("masa-root-ca"))) {
                strncpy(masa_root_ca_file, optarg, EST_MAX_FILE_LEN);
            }
            if (!strncmp(long_options[option_index].name,"masa-priv-key", strlen("masa-priv-key"))) {
                strncpy(masa_priv_key_file, optarg, EST_MAX_FILE_LEN);
            }
            
            rc = set_brski_masa_credentials(masa_root_ca_file, masa_priv_key_file);
            if (rc == -1) {
                printf("\nUnable to read and set the MASA root CA credentials\n");
            }
#endif
#ifdef HAVE_LIBCOAP
            if (!strncmp(long_options[option_index].name, "enable-coap",
                         strlen("enable-coap"))) {
                coap_mode = 1;
            }
            if (!strncmp(long_options[option_index].name, "dtls-handshake-timeout",
                         strlen("dtls-handshake-timeout"))) {
                dtls_handshake_timeout = atoi(optarg);
            }
            if (!strncmp(long_options[option_index].name, "dtls-handshake-mtu",
                         strlen("dtls-handshake-mtu"))) {
                dtls_handshake_mtu = atoi(optarg);
            }
            if (!strncmp(long_options[option_index].name, "dtls-session-max",
                         strlen("dtls-session-max"))) {
                dtls_session_max = atoi(optarg);
            }
#endif
            if (!strncmp(long_options[option_index].name,"perf-timers-on",
                         strlen("perf-timers-on"))) {
                perf_timers_on = 1;
            }
            break;
        case 'm':
            manual_enroll = 1;
            retry_period = atoi(optarg);
            break;
        case 'h':
            http_digest_auth = 1;
            break;
        case 'b':
            http_basic_auth = 1;
            break;
        case 'w':
            write_csr = 1;
            break;
        case 'n':
            http_auth_disable = 1;
            break;
        case 'o':
            disable_forced_http_auth = 1;
            break;
        case 'v':
            verbose = 1;
            break;
        case 'l':
            crl = 1;
            break;
        case 't':
            pop = 1;
            break;
        case '6':
            v6 = 1;
            break;
#ifndef DISABLE_PTHREADS
        case 'd':
            sleep_delay = atoi(optarg);
            break;
#endif
        case 'p':
            port_num = atoi(optarg);
            break;
        case 'c':
            strncpy(certfile, optarg, EST_MAX_FILE_LEN);
            break;
        case 'k':
            strncpy(keyfile, optarg, EST_MAX_FILE_LEN);
            break;
        case 'r':
            if (strnlen(optarg, MAX_REALM_LEN+1) > MAX_REALM_LEN) {
                printf("\nRealm value is too large.  Max is 32 characters\n");
                exit(1);
            }
            
            strncpy(realm, optarg, MAX_REALM_LEN);
            break;
        case 'f':
            /* turn FIPS on if user requested it
             * and exit if failure.
             */
            set_fips_return = FIPS_mode_set(1);
            if (set_fips_return != 1) {
                set_fips_error = ERR_get_error();
                printf("\nERROR WHILE SETTING FIPS MODE ON exiting ....\n");
                exit(1);
            } else {
                printf("\nRunning EST Sample Server with FIPS MODE = ON !\n");
            }
            ;
            break;
        default:
            show_usage_and_exit();
            break;
        }
    }
    argc -= optind;
    argv += optind;

    if (verbose) {
        print_version(stdout);
    }

    if (getenv("EST_CSR_ATTR")) {
        printf("\nUsing CSR Attributes: %s", getenv("EST_CSR_ATTR"));
    }

    if (!getenv("EST_CACERTS_RESP")) {
        printf("\nEST_CACERTS_RESP file not set, set this env variable to resolve");
        exit(1);
    }
    if (!getenv("EST_TRUSTED_CERTS")) {
        printf("\nEST_TRUSTED_CERTS file not set, set this env variable to resolve");
        exit(1);
    }

    /*
     * Read in the CA certificates
     */
    cacerts_len = read_binary_file(getenv("EST_CACERTS_RESP"), &cacerts_raw);
    if (cacerts_len <= 0) {
        printf("\nEST_CACERTS_RESP file could not be read\n");
        exit(1);
    }
    /*
     * Read in the trusted CA certificates for the local TLS context
     */
    if (getenv("EST_TRUSTED_CERTS")) {
        trustcerts_len = read_binary_file(getenv("EST_TRUSTED_CERTS"),
                                          &trustcerts);
        if (trustcerts_len <= 0) {
            printf("\nEST_TRUSTED_CERTS file could not be read\n");
            exit(1);
        }
    }

    est_apps_startup();

    /*
     * Read in the local server certificate
     */
    certin = BIO_new(BIO_s_file());
    if (BIO_read_filename(certin, certfile) <= 0) {
        printf("\nUnable to read server certificate file %s\n", certfile);
        exit(1);
    }
    /*
     * This reads the file, which is expected to be PEM encoded.  If you're using
     * DER encoded certs, you would invoke d2i_X509_bio() instead.
     */
    x = PEM_read_bio_X509(certin, NULL, NULL, NULL);
    if (x == NULL) {
        printf("\nError while reading PEM encoded server certificate file %s\n",
               certfile);
        exit(1);
    }
    BIO_free(certin);

    /*
     * Read in the server's private key
     */

    priv_key = read_private_key(keyfile, priv_key_cb);
    if (priv_key == NULL) {
        printf("\nError while reading PEM encoded server private key file %s\n",
               keyfile);
        ERR_print_errors_fp(stderr);
        exit(1);
    }

    bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
    if (!bio_err) {
        printf("\nBIO not working\n");
        exit(1);
    }

    if (verbose) {
        est_init_logger(EST_LOG_LVL_INFO, NULL);
        est_enable_backtrace(1);
    } else {
        est_init_logger(EST_LOG_LVL_ERR, NULL);
    }
    ectx = est_server_init(trustcerts, trustcerts_len, cacerts_raw, cacerts_len,
                           EST_CERT_FORMAT_PEM, realm, x, priv_key);
    if (!ectx) {
        printf("\nUnable to initialize EST context.  Aborting!!!\n");
        exit(1);
    }
    est_set_ex_data(ectx, &test_app_data);

    if (enforce_csr) {
        est_server_enforce_csrattr(ectx);
    }

    /*
     * Change the retry-after period.  This is not
     * necessary, it's only shown here as an example.
     */
    if (verbose)
        printf("\nRetry period being set to: %d \n", retry_period);
    est_server_set_retry_period(ectx, retry_period);

    if (crl) {
        est_enable_crl(ectx);
    }
    if (!pop) {
        if (verbose)
            printf("\nDisabling PoP check");
        est_server_disable_pop(ectx);
    }

    if (srp) {
        srp_db = SRP_VBASE_new(NULL);
        if (!srp_db) {
            printf("\nUnable allocate SRP verifier database.  Aborting!!!\n");
            exit(1);
        }
        if (SRP_VBASE_init(srp_db, vfile) != SRP_NO_ERROR) {
            printf("\nUnable initialize SRP verifier database.  Aborting!!!\n");
            exit(1);
        }

        if (est_server_enable_srp(ectx, &process_ssl_srp_auth)) {
            printf("\nUnable to enable SRP.  Aborting!!!\n");
            exit(1);
        }
    }

    if (est_set_ca_enroll_cb(ectx, &process_pkcs10_enrollment)) {
        printf(
            "\nUnable to set EST pkcs10 enrollment callback.  Aborting!!!\n");
        exit(1);
    }
    /*
     * We use the same handler for re-enrollment.  Our little toy
     * CA doesn't do anything special for re-enrollment.  A real
     * CA would need to implement the requirements in section
     * 4.2 of the EST draft.
     */
    if (est_set_ca_reenroll_cb(ectx, &process_pkcs10_enrollment)) {
        printf(
            "\nUnable to set EST pkcs10 enrollment callback.  Aborting!!!\n");
        exit(1);
    }

    /*
     * Set server-side key generation callback
     */
    if (est_set_server_side_keygen_enroll_cb(ectx, &process_srvr_side_keygen_pkcs10_enrollment)) {
        printf(
            "\nUnable to set EST server-side keygen enrollment callback.  Aborting!!!\n");
        exit(1);
    }
    if (est_server_set_key_generation_cb(ectx, &generate_private_key)) {
        printf(
                "\nUnable to set EST server-side key generation callback.  Aborting!!!\n");
        exit(1);
    }

    if (est_set_csr_cb(ectx, &process_csrattrs_request)) {
        printf("\nUnable to set EST CSR Attributes callback.  Aborting!!!\n");
        exit(1);
    }
#if ENABLE_BRSKI
    if (brski_mode) {
        /*
         * register the brski call backs.
         */
        if (est_set_brski_voucher_req_cb(ectx, &process_brski_voucher_request)) {
            printf(
                "\nUnable to set EST BRSKI voucher request callback.  Aborting!!!\n");
            exit(1);
        }
        if (est_set_brski_voucher_status_cb(ectx, &process_brski_voucher_status)) {
            printf(
                "\nUnable to set EST BRSKI voucher request callback.  Aborting!!!\n");
            exit(1);
        }
        if (est_set_brski_enroll_status_cb(ectx, &process_brski_enroll_status)) {
            printf(
                "\nUnable to set EST BRSKI voucher request callback.  Aborting!!!\n");
            exit(1);
        }

        /*
         * For EST /cacerts, the CA certs response can be processed two ways,
         * they can be provided to the EST library and the library responds
         * directly, or the application layer can provide a call back and
         * it provides the response buffer containing the CA certs.  The estserver
         * test app does it the first way, so the EST library responds directly.
         * With BRSKI, this response of the CA certs is contained in the voucher, so
         * the application layer needs to be responsible for preparing the response.
         * The following code is replicated from the EST library.
         */
        if (est_load_ca_certs(cacerts_raw, cacerts_len)) {
            printf("Failed to load CA certificates response buffer");
        }
    }
#endif    
    if (!http_auth_disable) {
        if (est_set_http_auth_cb(ectx, &process_http_auth)) {
            printf("\nUnable to set EST HTTP AUTH callback.  Aborting!!!\n");
            exit(1);
        }
    }
    if (disable_forced_http_auth) {
        if (verbose)
            printf(
                "\nDisabling HTTP authentication when TLS client auth succeeds\n");
        if (est_set_http_auth_required(ectx, HTTP_AUTH_NOT_REQUIRED)) {
            printf("\nUnable to disable required HTTP auth.  Aborting!!!\n");
            exit(1);
        }
    }

    if (http_digest_auth) {
        
        MD5_CTX c;
        int len;
        static unsigned char ha1_input_buf[32*3+2];
        unsigned char md[17];
        int i;
        
        rv = est_server_set_auth_mode(ectx, AUTH_DIGEST);
        if (rv != EST_ERR_NONE) {
            printf(
                "\nUnable to enable HTTP digest authentication.  Aborting!!!\n");
            exit(1);
        }

        /*
         * Cache away the realm value and build the HA1
         */
        strncpy(digest_user[1], realm, MAX_REALM_LEN);

        len = sprintf((char *)ha1_input_buf, "%s:%s:%s", "estuser", realm, "estpwd");
        MD5_Init(&c);
        MD5_Update(&c, ha1_input_buf, len);
        MD5_Final((unsigned char *)md, &c);

        printf("\nDigest HA1 value = ");
        memset(digest_user[2], 0, 32);
        for(i = 0; i < 16; i++){
            
            sprintf(&(digest_user[2][i*2]),"%.2x", (unsigned char) md[i]);
            printf("%c%c", digest_user[2][i*2], digest_user[2][i*2+1]);
        }
        printf("\n");
    }

    if (http_basic_auth) {
        rv = est_server_set_auth_mode(ectx, AUTH_BASIC);
        if (rv != EST_ERR_NONE) {
            printf(
                "\nUnable to enable HTTP basic authentication.  Aborting!!!\n");
            exit(1);
        }
    }

    if (http_token_auth) {
        rv = est_server_set_auth_mode(ectx, AUTH_TOKEN);
        if (rv != EST_ERR_NONE) {
            printf(
                "\nUnable to enable HTTP token authentication.  Aborting!!!\n");
            exit(1);
        }
    }

    if (enable_enhcd_cert_auth) {
        if (!set_cert_auth_ah_pwd) {
            strncpy(cert_auth_ah_pwd, DEFAULT_ENHCD_CERT_PWD, MAX_PWD_LEN);
        }
        if (set_cert_auth_local_nid) {
            nid = OBJ_txt2nid(local_nid);
            if (nid != NID_undef) {
                rv = est_server_enable_enhanced_cert_auth(
                    ectx, nid, (const char *)cert_auth_ah_pwd,
                    enhcd_cert_csr_check_on);
            } else {
                printf(
                    "\nUnknown subject field NID specified. See ASN1_OBJECT \n"
                    "long and short names that can be specified.\n");
                exit(1);
            }
        } else {
            rv = est_server_enable_enhanced_cert_auth(
                ectx, DEFAULT_ENHCD_CERT_LOCAL_PKI_NID,
                (const char *)cert_auth_ah_pwd, enhcd_cert_csr_check_on);
        }
        if (rv != EST_ERR_NONE) {
            printf("\nUnable to enable Enhanced Cert Authentication. "
                   "Aborting!!!\n");
            exit(1);
        }
        if (set_enhcd_cert_truststore || set_cert_auth_mfg_name) {
            /*
             * One cannot be present without the other to register a
             * manufacturer
             */
            if (!set_enhcd_cert_truststore || !set_cert_auth_mfg_name) {
                printf("\nBoth the manufacturer name and truststore file must\n"
                       "be provided to register a manufacturer\n");
                exit(1);
            }
            enhcd_cert_truststore_len =
                read_binary_file(mfg_truststore_file, &enhcd_cert_truststore);
            if (enhcd_cert_truststore_len <= 0) {
                printf("\nCould not read the Enhanced Cert Auth truststore "
                       "file\n");
                exit(1);
            }
            if (set_cert_auth_mfg_nid) {
                nid = OBJ_txt2nid(mfg_nid);
                if (nid != NID_undef) {
                    rv = est_server_enhanced_cert_auth_add_mfg_info(
                        ectx, mfg_name, nid, enhcd_cert_truststore,
                        enhcd_cert_truststore_len);
                } else {
                    printf("\nUnknown subject field NID specified. See "
                           "ASN1_OBJECT \n"
                           "long and short names that can be specified.\n");
                    exit(1);
                }
            } else {
                rv = est_server_enhanced_cert_auth_add_mfg_info(
                    ectx, mfg_name, DEFAULT_ENHCD_CERT_LOCAL_PKI_NID,
                    enhcd_cert_truststore, enhcd_cert_truststore_len);
            }
            if (rv != EST_ERR_NONE) {
                printf("\nUnable to register Enhanced Cert Auth manufacturer. "
                       "Aborting!!!\n");
                exit(1);
            }
        }
    } else {
        if (set_cert_auth_ah_pwd || set_cert_auth_local_nid ||
            set_cert_auth_mfg_name || set_enhcd_cert_truststore ||
            set_cert_auth_mfg_nid) {
            printf("Enhanced Cert Auth must be enabled to specify the following"
                   "parameters:\n");
            if (set_cert_auth_ah_pwd) {
                printf("- cert_auth_ah_pwd\n");
            }
            if (set_cert_auth_local_nid) {
                printf("- enhcd_cert_local_nid\n");
            }
            if (set_cert_auth_mfg_name) {
                printf("- enhcd_cert_mfg_name\n");
            }
            if (set_enhcd_cert_truststore) {
                printf("- enhcd_cert_mfg_truststore\n");
            }
            if (set_cert_auth_mfg_nid) {
                printf("- enhcd_cert_mfg_nid\n");
            }
            printf("\n");
            show_usage_and_exit();
        }
    }
    if (perf_timers_on) {
        est_enable_performance_timers(ectx);
    }
    /*
     * Set DH parameters for TLS
     */
    dh = get_dh1024dsa();
    if (dh) {
        est_server_set_dh_parms(ectx, dh);
    }
    DH_free(dh);

#ifdef HAVE_OLD_OPENSSL    
    /*
     * Install thread locking mechanism for OpenSSL
     */
    mutex_buf = malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
    if (!mutex_buf) {
        printf("Cannot allocate mutexes");
        exit(1);
    }
    for (i = 0; i < CRYPTO_num_locks(); i++)
        MUTEX_SETUP(mutex_buf[i]);
    CRYPTO_set_id_callback(id_function);
    CRYPTO_set_locking_callback(locking_function);
#endif
    printf("\nLaunching EST server...\n");

    if (coap_mode) {        
#if !(HAVE_LIBCOAP)
        printf("\nestserver not built with coap support and --enable-coap has been specified.\n");
        exit(1);
#else
        if (dtls_handshake_timeout != 0) {
            printf("\nSetting the DTLS handshake initial timeout value to: %d\n",  dtls_handshake_timeout);
            
            rv = est_server_set_dtls_handshake_timeout(ectx, dtls_handshake_timeout);
            if (rv != EST_ERR_NONE) {
                printf("\nUnable to set the DTLS handshake initial timeout value. "
                       "Aborting!!!\n");
                exit(1);
            }
        }
        
        if (dtls_handshake_mtu != 0) {
            printf("\nSetting the DTLS handshake MTU value to: %d\n",  dtls_handshake_mtu);
            
            rv = est_server_set_dtls_handshake_mtu(ectx, dtls_handshake_mtu);
            if (rv != EST_ERR_NONE) {
                printf("\nUnable to set the DTLS handshake MTU value. "
                       "Aborting!!!\n");
                exit(1);
            }
        }
        
        if (dtls_session_max != 0) {
            printf("\nSetting the DTLS session max value to: %d\n",  dtls_session_max);
            
            rv = est_server_set_dtls_session_max(ectx, dtls_session_max);
            if (rv != EST_ERR_NONE) {
                printf("\nUnable to set the DTLS session max value. "
                       "Aborting!!!\n");
                exit(1);
            }
        }
        
        coap_rc = est_server_coap_init_start(ectx, port_num);
        if (coap_rc != 0) {
            printf("\nFailed to init the coap library into server mode\n");
            exit(1);
        }
#endif
    }
    else {
        rv = est_server_start(ectx);
        if (rv != EST_ERR_NONE) {
            printf("\nFailed to init mg (rv=%d)\n", rv);
            exit(1);
        }
    }
    fflush(stdout);

    /*
     * Start the simple server, which opens a TCP
     * socket, waits for incoming connections, and
     * invokes the EST handler for each connection.
     *
     * If CoAP is enabled, then the master thread will
     * turn over control of the socket to the coap library
     */
    start_simple_server(ectx, port_num, sleep_delay, v6);
    
    cleanup();
    EVP_PKEY_free(priv_key);
    X509_free(x);
    return 0;
}

