/*
 *  OpenVPN -- An application to securely tunnel IP networks
 *             over a single TCP/UDP port, with support for SSL/TLS-based
 *             session authentication and key exchange,
 *             packet encryption, packet authentication, and
 *             packet compression.
 *
 *  Copyright (C) 2002-2018 OpenVPN Inc <sales@openvpn.net>
 *  Copyright (C) 2010-2018 Fox Crypto B.V. <openvpn@fox-it.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/**
 * @file Control Channel Verification Module
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#elif defined(_MSC_VER)
#include "config-msvc.h"
#endif

#include "syshead.h"

#include "base64.h"
#include "manage.h"
#include "otime.h"
#include "run_command.h"
#include "ssl_verify.h"
#include "ssl_verify_backend.h"

#ifdef ENABLE_CRYPTO_OPENSSL
#include "ssl_verify_openssl.h"
#endif

/** Maximum length of common name */
#define TLS_USERNAME_LEN 64

static void
string_mod_remap_name(char *str)
{
    string_mod(str, CC_PRINT, CC_CRLF, '_');
}

/*
 * Export the untrusted IP address and port to the environment
 */
static void
setenv_untrusted(struct tls_session *session)
{
    setenv_link_socket_actual(session->opt->es, "untrusted", &session->untrusted_addr, SA_IP_PORT);
}


/**
 *  Wipes the authentication token out of the memory, frees and cleans up related buffers and flags
 *
 *  @param multi  Pointer to a multi object holding the auth_token variables
 */
static void
wipe_auth_token(struct tls_multi *multi)
{
    if(multi)
    {
        if (multi->auth_token)
        {
            secure_memzero(multi->auth_token, AUTH_TOKEN_SIZE);
            free(multi->auth_token);
        }
        multi->auth_token = NULL;
        multi->auth_token_sent = false;
    }
}


/*
 * Remove authenticated state from all sessions in the given tunnel
 */
static void
tls_deauthenticate(struct tls_multi *multi)
{
    if (multi)
    {
        wipe_auth_token(multi);
        for (int i = 0; i < TM_SIZE; ++i)
        {
            for (int j = 0; j < KS_SIZE; ++j)
            {
                multi->session[i].key[j].authenticated = false;
            }
        }
    }
}

/*
 * Set the given session's common_name
 */
static void
set_common_name(struct tls_session *session, const char *common_name)
{
    if (session->common_name)
    {
        free(session->common_name);
        session->common_name = NULL;
#ifdef ENABLE_PF
        session->common_name_hashval = 0;
#endif
    }
    if (common_name)
    {
        /* FIXME: Last alloc will never be freed */
        session->common_name = string_alloc(common_name, NULL);
#ifdef ENABLE_PF
        {
            const uint32_t len = (uint32_t) strlen(common_name);
            if (len)
            {
                session->common_name_hashval = hash_func((const uint8_t *)common_name, len+1, 0);
            }
            else
            {
                session->common_name_hashval = 0;
            }
        }
#endif
    }
}

/*
 * Retrieve the common name for the given tunnel's active session. If the
 * common name is NULL or empty, return NULL if null is true, or "UNDEF" if
 * null is false.
 */
const char *
tls_common_name(const struct tls_multi *multi, const bool null)
{
    const char *ret = NULL;
    if (multi)
    {
        ret = multi->session[TM_ACTIVE].common_name;
    }
    if (ret && strlen(ret))
    {
        return ret;
    }
    else if (null)
    {
        return NULL;
    }
    else
    {
        return "UNDEF";
    }
}

/*
 * Lock the common name for the given tunnel.
 */
void
tls_lock_common_name(struct tls_multi *multi)
{
    const char *cn = multi->session[TM_ACTIVE].common_name;
    if (cn && !multi->locked_cn)
    {
        multi->locked_cn = string_alloc(cn, NULL);
    }
}

/*
 * Lock the username for the given tunnel
 */
static bool
tls_lock_username(struct tls_multi *multi, const char *username)
{
    if (multi->locked_username)
    {
        if (!username || strcmp(username, multi->locked_username))
        {
            msg(D_TLS_ERRORS, "TLS Auth Error: username attempted to change from '%s' to '%s' -- tunnel disabled",
                multi->locked_username,
                np(username));

            /* disable the tunnel */
            tls_deauthenticate(multi);
            return false;
        }
    }
    else
    {
        if (username)
        {
            multi->locked_username = string_alloc(username, NULL);
        }
    }
    return true;
}

const char *
tls_username(const struct tls_multi *multi, const bool null)
{
    const char *ret = NULL;
    if (multi)
    {
        ret = multi->locked_username;
    }
    if (ret && strlen(ret))
    {
        return ret;
    }
    else if (null)
    {
        return NULL;
    }
    else
    {
        return "UNDEF";
    }
}

void
cert_hash_remember(struct tls_session *session, const int error_depth,
                   const struct buffer *cert_hash)
{
    if (error_depth >= 0 && error_depth < MAX_CERT_DEPTH)
    {
        if (!session->cert_hash_set)
        {
            ALLOC_OBJ_CLEAR(session->cert_hash_set, struct cert_hash_set);
        }
        if (!session->cert_hash_set->ch[error_depth])
        {
            ALLOC_OBJ(session->cert_hash_set->ch[error_depth], struct cert_hash);
        }

        struct cert_hash *ch = session->cert_hash_set->ch[error_depth];
        ASSERT(sizeof(ch->sha256_hash) == BLEN(cert_hash));
        memcpy(ch->sha256_hash, BPTR(cert_hash), sizeof(ch->sha256_hash));
    }
}

void
cert_hash_free(struct cert_hash_set *chs)
{
    if (chs)
    {
        int i;
        for (i = 0; i < MAX_CERT_DEPTH; ++i)
        {
            free(chs->ch[i]);
        }
        free(chs);
    }
}

bool
cert_hash_compare(const struct cert_hash_set *chs1, const struct cert_hash_set *chs2)
{
    if (chs1 && chs2)
    {
        int i;
        for (i = 0; i < MAX_CERT_DEPTH; ++i)
        {
            const struct cert_hash *ch1 = chs1->ch[i];
            const struct cert_hash *ch2 = chs2->ch[i];

            if (!ch1 && !ch2)
            {
                continue;
            }
            else if (ch1 && ch2 && !memcmp(ch1->sha256_hash, ch2->sha256_hash,
                                           sizeof(ch1->sha256_hash)))
            {
                continue;
            }
            else
            {
                return false;
            }
        }
        return true;
    }
    else if (!chs1 && !chs2)
    {
        return true;
    }
    else
    {
        return false;
    }
}

static struct cert_hash_set *
cert_hash_copy(const struct cert_hash_set *chs)
{
    struct cert_hash_set *dest = NULL;
    if (chs)
    {
        int i;
        ALLOC_OBJ_CLEAR(dest, struct cert_hash_set);
        for (i = 0; i < MAX_CERT_DEPTH; ++i)
        {
            const struct cert_hash *ch = chs->ch[i];
            if (ch)
            {
                ALLOC_OBJ(dest->ch[i], struct cert_hash);
                memcpy(dest->ch[i]->sha256_hash, ch->sha256_hash,
                       sizeof(dest->ch[i]->sha256_hash));
            }
        }
    }
    return dest;
}
void
tls_lock_cert_hash_set(struct tls_multi *multi)
{
    const struct cert_hash_set *chs = multi->session[TM_ACTIVE].cert_hash_set;
    if (chs && !multi->locked_cert_hash_set)
    {
        multi->locked_cert_hash_set = cert_hash_copy(chs);
    }
}

/*
 * Returns the string associated with the given certificate type.
 */
static const char *
print_nsCertType(int type)
{
    switch (type)
    {
        case NS_CERT_CHECK_SERVER:
            return "SERVER";

        case NS_CERT_CHECK_CLIENT:
            return "CLIENT";

        default:
            return "?";
    }
}

/*
 * Verify the peer's certificate fields.
 *
 * @param opt the tls options to verify against
 * @param peer_cert the peer's certificate
 * @param subject the peer's extracted subject name
 * @param subject the peer's extracted common name
 */
static result_t
verify_peer_cert(const struct tls_options *opt, openvpn_x509_cert_t *peer_cert,
                 const char *subject, const char *common_name)
{
    /* verify certificate nsCertType */
    if (opt->ns_cert_type != NS_CERT_CHECK_NONE)
    {
        if (SUCCESS == x509_verify_ns_cert_type(peer_cert, opt->ns_cert_type))
        {
            msg(D_HANDSHAKE, "VERIFY OK: nsCertType=%s",
                print_nsCertType(opt->ns_cert_type));
        }
        else
        {
            msg(D_HANDSHAKE, "VERIFY nsCertType ERROR: %s, require nsCertType=%s",
                subject, print_nsCertType(opt->ns_cert_type));
            return FAILURE;             /* Reject connection */
        }
    }

    /* verify certificate ku */
    if (opt->remote_cert_ku[0] != 0)
    {
        if (SUCCESS == x509_verify_cert_ku(peer_cert, opt->remote_cert_ku, MAX_PARMS))
        {
            msg(D_HANDSHAKE, "VERIFY KU OK");
        }
        else
        {
            msg(D_HANDSHAKE, "VERIFY KU ERROR");
            return FAILURE;             /* Reject connection */
        }
    }

    /* verify certificate eku */
    if (opt->remote_cert_eku != NULL)
    {
        if (SUCCESS == x509_verify_cert_eku(peer_cert, opt->remote_cert_eku))
        {
            msg(D_HANDSHAKE, "VERIFY EKU OK");
        }
        else
        {
            msg(D_HANDSHAKE, "VERIFY EKU ERROR");
            return FAILURE;             /* Reject connection */
        }
    }

    /* verify X509 name or username against --verify-x509-[user]name */
    if (opt->verify_x509_type != VERIFY_X509_NONE)
    {
        if ( (opt->verify_x509_type == VERIFY_X509_SUBJECT_DN
              && strcmp(opt->verify_x509_name, subject) == 0)
             || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN
                 && strcmp(opt->verify_x509_name, common_name) == 0)
             || (opt->verify_x509_type == VERIFY_X509_SUBJECT_RDN_PREFIX
                 && strncmp(opt->verify_x509_name, common_name,
                            strlen(opt->verify_x509_name)) == 0) )
        {
            msg(D_HANDSHAKE, "VERIFY X509NAME OK: %s", subject);
        }
        else
        {
            msg(D_HANDSHAKE, "VERIFY X509NAME ERROR: %s, must be %s",
                subject, opt->verify_x509_name);
            return FAILURE;             /* Reject connection */
        }
    }

    return SUCCESS;
}

/*
 * Export the subject, common_name, and raw certificate fields to the
 * environment for later verification by scripts and plugins.
 */
static void
verify_cert_set_env(struct env_set *es, openvpn_x509_cert_t *peer_cert, int cert_depth,
                    const char *subject, const char *common_name,
                    const struct x509_track *x509_track)
{
    char envname[64];
    char *serial = NULL;
    struct gc_arena gc = gc_new();

    /* Save X509 fields in environment */
    if (x509_track)
    {
        x509_setenv_track(x509_track, es, cert_depth, peer_cert);
    }
    else
    {
        x509_setenv(es, cert_depth, peer_cert);
    }

    /* export subject name string as environmental variable */
    openvpn_snprintf(envname, sizeof(envname), "tls_id_%d", cert_depth);
    setenv_str(es, envname, subject);

#if 0
    /* export common name string as environmental variable */
    openvpn_snprintf(envname, sizeof(envname), "tls_common_name_%d", cert_depth);
    setenv_str(es, envname, common_name);
#endif

    /* export X509 cert fingerprints */
    {
        struct buffer sha1 = x509_get_sha1_fingerprint(peer_cert, &gc);
        struct buffer sha256 = x509_get_sha256_fingerprint(peer_cert, &gc);

        openvpn_snprintf(envname, sizeof(envname), "tls_digest_%d", cert_depth);
        setenv_str(es, envname,
                   format_hex_ex(BPTR(&sha1), BLEN(&sha1), 0, 1, ":", &gc));

        openvpn_snprintf(envname, sizeof(envname), "tls_digest_sha256_%d",
                         cert_depth);
        setenv_str(es, envname,
                   format_hex_ex(BPTR(&sha256), BLEN(&sha256), 0, 1, ":", &gc));
    }

    /* export serial number as environmental variable */
    serial = backend_x509_get_serial(peer_cert, &gc);
    openvpn_snprintf(envname, sizeof(envname), "tls_serial_%d", cert_depth);
    setenv_str(es, envname, serial);

    /* export serial number in hex as environmental variable */
    serial = backend_x509_get_serial_hex(peer_cert, &gc);
    openvpn_snprintf(envname, sizeof(envname), "tls_serial_hex_%d", cert_depth);
    setenv_str(es, envname, serial);

    gc_free(&gc);
}

/*
 * call --tls-verify plug-in(s)
 */
static result_t
verify_cert_call_plugin(const struct plugin_list *plugins, struct env_set *es,
                        int cert_depth, openvpn_x509_cert_t *cert, char *subject)
{
    if (plugin_defined(plugins, OPENVPN_PLUGIN_TLS_VERIFY))
    {
        int ret;
        struct argv argv = argv_new();

        argv_printf(&argv, "%d %s", cert_depth, subject);

        ret = plugin_call_ssl(plugins, OPENVPN_PLUGIN_TLS_VERIFY, &argv, NULL, es, cert_depth, cert);

        argv_reset(&argv);

        if (ret == OPENVPN_PLUGIN_FUNC_SUCCESS)
        {
            msg(D_HANDSHAKE, "VERIFY PLUGIN OK: depth=%d, %s",
                cert_depth, subject);
        }
        else
        {
            msg(D_HANDSHAKE, "VERIFY PLUGIN ERROR: depth=%d, %s",
                cert_depth, subject);
            return FAILURE;             /* Reject connection */
        }
    }
    return SUCCESS;
}

static const char *
verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, struct gc_arena *gc)
{
    FILE *peercert_file;
    const char *peercert_filename = "";

    /* create tmp file to store peer cert */
    if (!tmp_dir
        || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc)))
    {
        msg(M_NONFATAL, "Failed to create peer cert file");
        return NULL;
    }

    /* write peer-cert in tmp-file */
    peercert_file = fopen(peercert_filename, "w+");
    if (!peercert_file)
    {
        msg(M_NONFATAL|M_ERRNO, "Failed to open temporary file: %s",
            peercert_filename);
        return NULL;
    }

    if (SUCCESS != x509_write_pem(peercert_file, peercert))
    {
        msg(M_NONFATAL, "Error writing PEM file containing certificate");
        (void) platform_unlink(peercert_filename);
        peercert_filename = NULL;
    }

    fclose(peercert_file);
    return peercert_filename;
}


/*
 * run --tls-verify script
 */
static result_t
verify_cert_call_command(const char *verify_command, struct env_set *es,
                         int cert_depth, openvpn_x509_cert_t *cert, char *subject, const char *verify_export_cert)
{
    const char *tmp_file = NULL;
    int ret;
    struct gc_arena gc = gc_new();
    struct argv argv = argv_new();

    setenv_str(es, "script_type", "tls-verify");

    if (verify_export_cert)
    {
        tmp_file = verify_cert_export_cert(cert, verify_export_cert, &gc);
        if (!tmp_file)
        {
            ret = false;
            goto cleanup;
        }
        setenv_str(es, "peer_cert", tmp_file);
    }

    argv_parse_cmd(&argv, verify_command);
    argv_printf_cat(&argv, "%d %s", cert_depth, subject);

    argv_msg_prefix(D_TLS_DEBUG, &argv, "TLS: executing verify command");
    ret = openvpn_run_script(&argv, es, 0, "--tls-verify script");

    if (verify_export_cert)
    {
        if (tmp_file)
        {
            platform_unlink(tmp_file);
        }
    }

cleanup:
    gc_free(&gc);
    argv_reset(&argv);

    if (ret)
    {
        msg(D_HANDSHAKE, "VERIFY SCRIPT OK: depth=%d, %s",
            cert_depth, subject);
        return SUCCESS;
    }

    msg(D_HANDSHAKE, "VERIFY SCRIPT ERROR: depth=%d, %s",
        cert_depth, subject);
    return FAILURE;             /* Reject connection */
}

/*
 * check peer cert against CRL directory
 */
static result_t
verify_check_crl_dir(const char *crl_dir, openvpn_x509_cert_t *cert)
{
    result_t ret = FAILURE;
    char fn[256];
    int fd = -1;
    struct gc_arena gc = gc_new();

    char *serial = backend_x509_get_serial(cert, &gc);

    if (!openvpn_snprintf(fn, sizeof(fn), "%s%c%s", crl_dir, OS_SPECIFIC_DIRSEP, serial))
    {
        msg(D_HANDSHAKE, "VERIFY CRL: filename overflow");
        goto cleanup;
    }
    fd = platform_open(fn, O_RDONLY, 0);
    if (fd >= 0)
    {
        msg(D_HANDSHAKE, "VERIFY CRL: certificate serial number %s is revoked", serial);
        goto cleanup;
    }

    ret = SUCCESS;

cleanup:

    if (fd != -1)
    {
        close(fd);
    }
    gc_free(&gc);
    return ret;
}

result_t
verify_cert(struct tls_session *session, openvpn_x509_cert_t *cert, int cert_depth)
{
    result_t ret = FAILURE;
    char *subject = NULL;
    char common_name[TLS_USERNAME_LEN+1] = {0}; /* null-terminated */
    const struct tls_options *opt;
    struct gc_arena gc = gc_new();

    opt = session->opt;
    ASSERT(opt);

    session->verified = false;

    /* get the X509 name */
    subject = x509_get_subject(cert, &gc);
    if (!subject)
    {
        msg(D_TLS_ERRORS, "VERIFY ERROR: depth=%d, could not extract X509 "
            "subject string from certificate", cert_depth);
        goto cleanup;
    }

    /* enforce character class restrictions in X509 name */
    string_mod_remap_name(subject);
    string_replace_leading(subject, '-', '_');

    /* extract the username (default is CN) */
    if (SUCCESS != backend_x509_get_username(common_name, sizeof(common_name),
                                             opt->x509_username_field, cert))
    {
        if (!cert_depth)
        {
            msg(D_TLS_ERRORS, "VERIFY ERROR: could not extract %s from X509 "
                "subject string ('%s') -- note that the username length is "
                "limited to %d characters",
                opt->x509_username_field,
                subject,
                TLS_USERNAME_LEN);
            goto cleanup;
        }
    }

    /* enforce character class restrictions in common name */
    string_mod_remap_name(common_name);

    /* warn if cert chain is too deep */
    if (cert_depth >= MAX_CERT_DEPTH)
    {
        msg(D_TLS_ERRORS, "TLS Error: Convoluted certificate chain detected with depth [%d] greater than %d", cert_depth, MAX_CERT_DEPTH);
        goto cleanup;                   /* Reject connection */
    }

    /* verify level 1 cert, i.e. the CA that signed our leaf cert */
    if (cert_depth == 1 && opt->verify_hash)
    {
        struct buffer ca_hash = {0};

        switch (opt->verify_hash_algo)
        {
        case MD_SHA1:
            ca_hash = x509_get_sha1_fingerprint(cert, &gc);
            break;

        case MD_SHA256:
            ca_hash = x509_get_sha256_fingerprint(cert, &gc);
            break;

        default:
            /* This should normally not happen at all; the algorithm used
             * is parsed by add_option() [options.c] and set to a predefined
             * value in an enumerated type.  So if this unlikely scenario
             * happens, consider this a failure
             */
            msg(M_WARN, "Unexpected invalid algorithm used with "
                "--verify-hash (%i)", opt->verify_hash_algo);
            ret = FAILURE;
            goto cleanup;
        }

        if (memcmp(BPTR(&ca_hash), opt->verify_hash, BLEN(&ca_hash)))
        {
            msg(D_TLS_ERRORS, "TLS Error: level-1 certificate hash verification failed");
            goto cleanup;
        }
    }

    /* save common name in session object */
    if (cert_depth == 0)
    {
        set_common_name(session, common_name);
    }

    session->verify_maxlevel = max_int(session->verify_maxlevel, cert_depth);

    /* export certificate values to the environment */
    verify_cert_set_env(opt->es, cert, cert_depth, subject, common_name,
                        opt->x509_track);

    /* export current untrusted IP */
    setenv_untrusted(session);

    /* If this is the peer's own certificate, verify it */
    if (cert_depth == 0 && SUCCESS != verify_peer_cert(opt, cert, subject, common_name))
    {
        goto cleanup;
    }

    /* call --tls-verify plug-in(s), if registered */
    if (SUCCESS != verify_cert_call_plugin(opt->plugins, opt->es, cert_depth, cert, subject))
    {
        goto cleanup;
    }

    /* run --tls-verify script */
    if (opt->verify_command && SUCCESS != verify_cert_call_command(opt->verify_command,
                                                                   opt->es, cert_depth, cert, subject, opt->verify_export_cert))
    {
        goto cleanup;
    }

    /* check peer cert against CRL */
    if (opt->crl_file)
    {
        if (opt->ssl_flags & SSLF_CRL_VERIFY_DIR)
        {
            if (SUCCESS != verify_check_crl_dir(opt->crl_file, cert))
            {
                goto cleanup;
            }
        }
        else
        {
            if (tls_verify_crl_missing(opt))
            {
                msg(D_TLS_ERRORS, "VERIFY ERROR: CRL not loaded");
                goto cleanup;
            }
        }
    }

    msg(D_HANDSHAKE, "VERIFY OK: depth=%d, %s", cert_depth, subject);
    session->verified = true;
    ret = SUCCESS;

cleanup:

    if (ret != SUCCESS)
    {
        tls_clear_error(); /* always? */
        session->verified = false; /* double sure? */
    }
    gc_free(&gc);

    return ret;
}

/* ***************************************************************************
* Functions for the management of deferred authentication when using
* user/password authentication.
*************************************************************************** */

#ifdef ENABLE_DEF_AUTH
/* key_state_test_auth_control_file return values,
 * NOTE: acf_merge indexing depends on these values */
#define ACF_UNDEFINED 0
#define ACF_SUCCEEDED 1
#define ACF_DISABLED  2
#define ACF_FAILED    3
#endif

#ifdef MANAGEMENT_DEF_AUTH
void
man_def_auth_set_client_reason(struct tls_multi *multi, const char *client_reason)
{
    if (multi->client_reason)
    {
        free(multi->client_reason);
        multi->client_reason = NULL;
    }
    if (client_reason && strlen(client_reason))
    {
        /* FIXME: Last alloc will never be freed */
        multi->client_reason = string_alloc(client_reason, NULL);
    }
}

static inline unsigned int
man_def_auth_test(const struct key_state *ks)
{
    if (management_enable_def_auth(management))
    {
        return ks->mda_status;
    }
    else
    {
        return ACF_DISABLED;
    }
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */

#ifdef PLUGIN_DEF_AUTH

/*
 * auth_control_file functions
 */

void
key_state_rm_auth_control_file(struct key_state *ks)
{
    if (ks && ks->auth_control_file)
    {
        platform_unlink(ks->auth_control_file);
        free(ks->auth_control_file);
        ks->auth_control_file = NULL;
    }
}

static bool
key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options *opt)
{
    struct gc_arena gc = gc_new();

    key_state_rm_auth_control_file(ks);
    const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc);
    if (acf)
    {
        ks->auth_control_file = string_alloc(acf, NULL);
        setenv_str(opt->es, "auth_control_file", ks->auth_control_file);
    }

    gc_free(&gc);
    return acf;
}

static unsigned int
key_state_test_auth_control_file(struct key_state *ks)
{
    if (ks && ks->auth_control_file)
    {
        unsigned int ret = ks->auth_control_status;
        if (ret == ACF_UNDEFINED)
        {
            FILE *fp = fopen(ks->auth_control_file, "r");
            if (fp)
            {
                const int c = fgetc(fp);
                if (c == '1')
                {
                    ret = ACF_SUCCEEDED;
                }
                else if (c == '0')
                {
                    ret = ACF_FAILED;
                }
                fclose(fp);
                ks->auth_control_status = ret;
            }
        }
        return ret;
    }
    return ACF_DISABLED;
}

#endif /* ifdef PLUGIN_DEF_AUTH */

/*
 * Return current session authentication state.  Return
 * value is TLS_AUTHENTICATION_x.
 */

int
tls_authentication_status(struct tls_multi *multi, const int latency)
{
    bool deferred = false;
    bool success = false;
    bool active = false;

#ifdef ENABLE_DEF_AUTH
    static const unsigned char acf_merge[] =
    {
        ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_UNDEFINED */
        ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_SUCCEEDED */
        ACF_UNDEFINED, /* s1=ACF_UNDEFINED s2=ACF_DISABLED */
        ACF_FAILED,  /* s1=ACF_UNDEFINED s2=ACF_FAILED */
        ACF_UNDEFINED, /* s1=ACF_SUCCEEDED s2=ACF_UNDEFINED */
        ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_SUCCEEDED */
        ACF_SUCCEEDED, /* s1=ACF_SUCCEEDED s2=ACF_DISABLED */
        ACF_FAILED,  /* s1=ACF_SUCCEEDED s2=ACF_FAILED */
        ACF_UNDEFINED, /* s1=ACF_DISABLED  s2=ACF_UNDEFINED */
        ACF_SUCCEEDED, /* s1=ACF_DISABLED  s2=ACF_SUCCEEDED */
        ACF_DISABLED, /* s1=ACF_DISABLED  s2=ACF_DISABLED */
        ACF_FAILED,  /* s1=ACF_DISABLED  s2=ACF_FAILED */
        ACF_FAILED,  /* s1=ACF_FAILED    s2=ACF_UNDEFINED */
        ACF_FAILED,  /* s1=ACF_FAILED    s2=ACF_SUCCEEDED */
        ACF_FAILED,  /* s1=ACF_FAILED    s2=ACF_DISABLED */
        ACF_FAILED   /* s1=ACF_FAILED    s2=ACF_FAILED */
    };
#endif /* ENABLE_DEF_AUTH */

    if (multi)
    {
        int i;

#ifdef ENABLE_DEF_AUTH
        if (latency && multi->tas_last && multi->tas_last + latency >= now)
        {
            return TLS_AUTHENTICATION_UNDEFINED;
        }
        multi->tas_last = now;
#endif /* ENABLE_DEF_AUTH */

        for (i = 0; i < KEY_SCAN_SIZE; ++i)
        {
            struct key_state *ks = multi->key_scan[i];
            if (DECRYPT_KEY_ENABLED(multi, ks))
            {
                active = true;
                if (ks->authenticated)
                {
#ifdef ENABLE_DEF_AUTH
                    unsigned int s1 = ACF_DISABLED;
                    unsigned int s2 = ACF_DISABLED;
#ifdef PLUGIN_DEF_AUTH
                    s1 = key_state_test_auth_control_file(ks);
#endif /* PLUGIN_DEF_AUTH */
#ifdef MANAGEMENT_DEF_AUTH
                    s2 = man_def_auth_test(ks);
#endif /* MANAGEMENT_DEF_AUTH */
                    ASSERT(s1 < 4 && s2 < 4);
                    switch (acf_merge[(s1<<2) + s2])
                    {
                        case ACF_SUCCEEDED:
                        case ACF_DISABLED:
                            success = true;
                            ks->auth_deferred = false;
                            break;

                        case ACF_UNDEFINED:
                            if (now < ks->auth_deferred_expire)
                            {
                                deferred = true;
                            }
                            break;

                        case ACF_FAILED:
                            ks->authenticated = false;
                            break;

                        default:
                            ASSERT(0);
                    }
#else /* !ENABLE_DEF_AUTH */
                    success = true;
#endif /* ENABLE_DEF_AUTH */
                }
            }
        }
    }

#if 0
    dmsg(D_TLS_ERRORS, "TAS: a=%d s=%d d=%d", active, success, deferred);
#endif

    if (success)
    {
        return TLS_AUTHENTICATION_SUCCEEDED;
    }
    else if (!active || deferred)
    {
        return TLS_AUTHENTICATION_DEFERRED;
    }
    else
    {
        return TLS_AUTHENTICATION_FAILED;
    }
}

#ifdef MANAGEMENT_DEF_AUTH
/*
 * For deferred auth, this is where the management interface calls (on server)
 * to indicate auth failure/success.
 */
bool
tls_authenticate_key(struct tls_multi *multi, const unsigned int mda_key_id, const bool auth, const char *client_reason)
{
    bool ret = false;
    if (multi)
    {
        int i;
        man_def_auth_set_client_reason(multi, client_reason);
        for (i = 0; i < KEY_SCAN_SIZE; ++i)
        {
            struct key_state *ks = multi->key_scan[i];
            if (ks->mda_key_id == mda_key_id)
            {
                ks->mda_status = auth ? ACF_SUCCEEDED : ACF_FAILED;
                ret = true;
            }
        }
    }
    return ret;
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */


/* ****************************************************************************
 * Functions to verify username and password
 *
 * Authenticate a client using username/password.
 * Runs on server.
 *
 * If you want to add new authentication methods,
 * this is the place to start.
 *************************************************************************** */

/*
 * Verify the user name and password using a script
 */
static bool
verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
{
    struct gc_arena gc = gc_new();
    struct argv argv = argv_new();
    const char *tmp_file = "";
    bool ret = false;

    /* Is username defined? */
    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
    {
        /* Set environmental variables prior to calling script */
        setenv_str(session->opt->es, "script_type", "user-pass-verify");

        if (session->opt->auth_user_pass_verify_script_via_file)
        {
            struct status_output *so;

            tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up",
                                                 &gc);
            if (tmp_file)
            {
                so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
                status_printf(so, "%s", up->username);
                status_printf(so, "%s", up->password);
                if (!status_close(so))
                {
                    msg(D_TLS_ERRORS, "TLS Auth Error: could not write username/password to file: %s",
                        tmp_file);
                    goto done;
                }
            }
            else
            {
                msg(D_TLS_ERRORS, "TLS Auth Error: could not create write "
                    "username/password to temp file");
            }
        }
        else
        {
            setenv_str(session->opt->es, "username", up->username);
            setenv_str(session->opt->es, "password", up->password);
        }

        /* setenv incoming cert common name for script */
        setenv_str(session->opt->es, "common_name", session->common_name);

        /* setenv client real IP address */
        setenv_untrusted(session);

        /* format command line */
        argv_parse_cmd(&argv, session->opt->auth_user_pass_verify_script);
        argv_printf_cat(&argv, "%s", tmp_file);

        /* call command */
        ret = openvpn_run_script(&argv, session->opt->es, 0,
                                 "--auth-user-pass-verify");

        if (!session->opt->auth_user_pass_verify_script_via_file)
        {
            setenv_del(session->opt->es, "password");
        }
    }
    else
    {
        msg(D_TLS_ERRORS, "TLS Auth Error: peer provided a blank username");
    }

done:
    if (tmp_file && strlen(tmp_file) > 0)
    {
        platform_unlink(tmp_file);
    }

    argv_reset(&argv);
    gc_free(&gc);
    return ret;
}

/*
 * Verify the username and password using a plugin
 */
static int
verify_user_pass_plugin(struct tls_session *session, const struct user_pass *up)
{
    int retval = OPENVPN_PLUGIN_FUNC_ERROR;
#ifdef PLUGIN_DEF_AUTH
    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */
#endif

    /* Is username defined? */
    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
    {
        /* set username/password in private env space */
        setenv_str(session->opt->es, "username", up->username);
        setenv_str(session->opt->es, "password", up->password);

        /* setenv incoming cert common name for script */
        setenv_str(session->opt->es, "common_name", session->common_name);

        /* setenv client real IP address */
        setenv_untrusted(session);

#ifdef PLUGIN_DEF_AUTH
        /* generate filename for deferred auth control file */
        if (!key_state_gen_auth_control_file(ks, session->opt))
        {
            msg (D_TLS_ERRORS, "TLS Auth Error (%s): "
                 "could not create deferred auth control file", __func__);
            goto cleanup;
        }
#endif

        /* call command */
        retval = plugin_call(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es);

#ifdef PLUGIN_DEF_AUTH
        /* purge auth control filename (and file itself) for non-deferred returns */
        if (retval != OPENVPN_PLUGIN_FUNC_DEFERRED)
        {
            key_state_rm_auth_control_file(ks);
        }
#endif

        setenv_del(session->opt->es, "password");
    }
    else
    {
        msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_plugin): peer provided a blank username");
    }

cleanup:
    return retval;
}


#ifdef MANAGEMENT_DEF_AUTH
/*
 * MANAGEMENT_DEF_AUTH internal ssl_verify.c status codes
 */
#define KMDA_ERROR   0
#define KMDA_SUCCESS 1
#define KMDA_UNDEF   2
#define KMDA_DEF     3

static int
verify_user_pass_management(struct tls_session *session, const struct user_pass *up)
{
    int retval = KMDA_ERROR;
    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */

    /* Is username defined? */
    if ((session->opt->ssl_flags & SSLF_AUTH_USER_PASS_OPTIONAL) || strlen(up->username))
    {
        /* set username/password in private env space */
        setenv_str(session->opt->es, "username", up->username);
        setenv_str(session->opt->es, "password", up->password);

        /* setenv incoming cert common name for script */
        setenv_str(session->opt->es, "common_name", session->common_name);

        /* setenv client real IP address */
        setenv_untrusted(session);

        if (management)
        {
            management_notify_client_needing_auth(management, ks->mda_key_id, session->opt->mda_context, session->opt->es);
        }

        setenv_del(session->opt->es, "password");

        retval = KMDA_SUCCESS;
    }
    else
    {
        msg(D_TLS_ERRORS, "TLS Auth Error (verify_user_pass_management): peer provided a blank username");
    }

    return retval;
}
#endif /* ifdef MANAGEMENT_DEF_AUTH */

/*
 * Main username/password verification entry point
 */
void
verify_user_pass(struct user_pass *up, struct tls_multi *multi,
                 struct tls_session *session)
{
    int s1 = OPENVPN_PLUGIN_FUNC_SUCCESS;
    bool s2 = true;
    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */

#ifdef MANAGEMENT_DEF_AUTH
    int man_def_auth = KMDA_UNDEF;

    if (management_enable_def_auth(management))
    {
        man_def_auth = KMDA_DEF;
    }
#endif

    /* enforce character class restrictions in username/password */
    string_mod_remap_name(up->username);
    string_mod(up->password, CC_PRINT, CC_CRLF, '_');

    /* If server is configured with --auth-gen-token and we have an
     * authentication token for this client, this authentication
     * round will be done internally using the token instead of
     * calling any external authentication modules.
     */
    if (session->opt->auth_token_generate && multi->auth_token_sent
        && NULL != multi->auth_token)
    {
        unsigned int ssl_flags = session->opt->ssl_flags;

        /* Ensure that the username has not changed */
        if (!tls_lock_username(multi, up->username))
        {
            /* auth-token cleared in tls_lock_username() on failure */
            ks->authenticated = false;
            return;
        }

        /* If auth-token lifetime has been enabled,
         * ensure the token has not expired
         */
        if (session->opt->auth_token_lifetime > 0
            && (multi->auth_token_tstamp + session->opt->auth_token_lifetime) < now)
        {
            msg(D_HANDSHAKE, "Auth-token for client expired\n");
            wipe_auth_token(multi);
            ks->authenticated = false;
            return;
        }

        /* The core authentication of the token itself */
        if (memcmp_constant_time(multi->auth_token, up->password,
                                 strlen(multi->auth_token)) != 0)
        {
            ks->authenticated = false;
            tls_deauthenticate(multi);

            msg(D_TLS_ERRORS, "TLS Auth Error: Auth-token verification "
                "failed for username '%s' %s", up->username,
                (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
        }
        else
        {
            ks->authenticated = true;

            if (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME)
            {
                set_common_name(session, up->username);
            }
            msg(D_HANDSHAKE, "TLS: Username/auth-token authentication "
                "succeeded for username '%s' %s",
                up->username,
                (ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
        }
        return;
    }

    /* call plugin(s) and/or script */
#ifdef MANAGEMENT_DEF_AUTH
    if (man_def_auth == KMDA_DEF)
    {
        man_def_auth = verify_user_pass_management(session, up);
    }
#endif
    if (plugin_defined(session->opt->plugins, OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY))
    {
        s1 = verify_user_pass_plugin(session, up);
    }
    if (session->opt->auth_user_pass_verify_script)
    {
        s2 = verify_user_pass_script(session, up);
    }

    /* check sizing of username if it will become our common name */
    if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) && strlen(up->username) > TLS_USERNAME_LEN)
    {
        msg(D_TLS_ERRORS, "TLS Auth Error: --username-as-common name specified and username is longer than the maximum permitted Common Name length of %d characters", TLS_USERNAME_LEN);
        s1 = OPENVPN_PLUGIN_FUNC_ERROR;
    }

    /* auth succeeded? */
    if ((s1 == OPENVPN_PLUGIN_FUNC_SUCCESS
#ifdef PLUGIN_DEF_AUTH
         || s1 == OPENVPN_PLUGIN_FUNC_DEFERRED
#endif
         ) && s2
#ifdef MANAGEMENT_DEF_AUTH
        && man_def_auth != KMDA_ERROR
#endif
        && tls_lock_username(multi, up->username))
    {
        ks->authenticated = true;
#ifdef PLUGIN_DEF_AUTH
        if (s1 == OPENVPN_PLUGIN_FUNC_DEFERRED)
        {
            ks->auth_deferred = true;
        }
#endif
#ifdef MANAGEMENT_DEF_AUTH
        if (man_def_auth != KMDA_UNDEF)
        {
            ks->auth_deferred = true;
        }
#endif

        if ((session->opt->auth_token_generate) && (NULL == multi->auth_token))
        {
            /* Server is configured with --auth-gen-token but no token has yet
             * been generated for this client.  Generate one and save it.
             */
            uint8_t tok[AUTH_TOKEN_SIZE];

            if (!rand_bytes(tok, AUTH_TOKEN_SIZE))
            {
                msg( M_FATAL, "Failed to get enough randomness for "
                     "authentication token");
            }

            /* The token should be longer than the input when
             * being base64 encoded
             */
            ASSERT(openvpn_base64_encode(tok, AUTH_TOKEN_SIZE,
                                         &multi->auth_token) > AUTH_TOKEN_SIZE);
            multi->auth_token_tstamp = now;
            dmsg(D_SHOW_KEYS, "Generated token for client: %s",
                 multi->auth_token);
        }

        if ((session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME))
        {
            set_common_name(session, up->username);
        }

#ifdef ENABLE_DEF_AUTH
        msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
            ks->auth_deferred ? "deferred" : "succeeded",
            up->username,
            (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
#else
        msg(D_HANDSHAKE, "TLS: Username/Password authentication %s for username '%s' %s",
            "succeeded",
            up->username,
            (session->opt->ssl_flags & SSLF_USERNAME_AS_COMMON_NAME) ? "[CN SET]" : "");
#endif
    }
    else
    {
        msg(D_TLS_ERRORS, "TLS Auth Error: Auth Username/Password verification failed for peer");
    }
}

void
verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)
{
    struct key_state *ks = &session->key[KS_PRIMARY];      /* primary key */

    /* While it shouldn't really happen, don't allow the common name to be NULL */
    if (!session->common_name)
    {
        set_common_name(session, "");
    }

    /* Don't allow the CN to change once it's been locked */
    if (ks->authenticated && multi->locked_cn)
    {
        const char *cn = session->common_name;
        if (cn && strcmp(cn, multi->locked_cn))
        {
            msg(D_TLS_ERRORS, "TLS Auth Error: TLS object CN attempted to change from '%s' to '%s' -- tunnel disabled",
                multi->locked_cn,
                cn);

            /* change the common name back to its original value and disable the tunnel */
            set_common_name(session, multi->locked_cn);
            tls_deauthenticate(multi);
        }
    }

    /* Don't allow the cert hashes to change once they have been locked */
    if (ks->authenticated && multi->locked_cert_hash_set)
    {
        const struct cert_hash_set *chs = session->cert_hash_set;
        if (chs && !cert_hash_compare(chs, multi->locked_cert_hash_set))
        {
            msg(D_TLS_ERRORS, "TLS Auth Error: TLS object CN=%s client-provided SSL certs unexpectedly changed during mid-session reauth",
                session->common_name);

            /* disable the tunnel */
            tls_deauthenticate(multi);
        }
    }

    /* verify --client-config-dir based authentication */
    if (ks->authenticated && session->opt->client_config_dir_exclusive)
    {
        struct gc_arena gc = gc_new();

        const char *cn = session->common_name;
        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive,
                                             cn, &gc);
        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))
        {
            ks->authenticated = false;
            wipe_auth_token(multi);
            msg(D_TLS_ERRORS, "TLS Auth Error: --client-config-dir authentication failed for common name '%s' file='%s'",
                session->common_name,
                path ? path : "UNDEF");
        }

        gc_free(&gc);
    }
}

void
tls_x509_clear_env(struct env_set *es)
{
    struct env_item *item = es->list;
    while (item)
    {
        struct env_item *next = item->next;
        if (item->string
            && 0 == strncmp("X509_", item->string, strlen("X509_")))
        {
            env_set_del(es, item->string);
        }
        item = next;
    }
}