lib/ssl/sslsock.c
author Martin Thomson <mt@lowentropy.net>
Fri, 15 Mar 2019 09:07:53 +1100
changeset 15052 03d7bcade60aa49fa7561215c83c176d0709b200
parent 15036 5ea3ab44389052cd2a8174350b86fb8fdadce075
child 15126 0556b3040451e8a7ca0a499005993a95921754d4
permissions -rw-r--r--
Bug 1529813 - Expose Hkdf-Expand-Label with mechanism, r=ekr Summary: It turns out that leaf keys sometimes need to be exposed with different mechanisms and sizes. The default function provides something good enough for use with the AEAD functions that were exposed, but if you want to use the key directly, that isn't enough. So here we are: new arguments for specifying the mechanism and key size are needed. Reviewers: ekr Tags: #secure-revision Bug #: 1529813 Differential Revision: https://phabricator.services.mozilla.com/D23596

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * vtables (and methods that call through them) for the 4 types of
 * SSLSockets supported.  Only one type is still supported.
 * Various other functions.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "seccomon.h"
#include "cert.h"
#include "keyhi.h"
#include "ssl.h"
#include "sslexp.h"
#include "sslimpl.h"
#include "sslproto.h"
#include "nspr.h"
#include "private/pprio.h"
#include "nss.h"
#include "pk11pqg.h"
#include "pk11pub.h"
#include "tls13esni.h"

static const sslSocketOps ssl_default_ops = { /* No SSL. */
                                              ssl_DefConnect,
                                              NULL,
                                              ssl_DefBind,
                                              ssl_DefListen,
                                              ssl_DefShutdown,
                                              ssl_DefClose,
                                              ssl_DefRecv,
                                              ssl_DefSend,
                                              ssl_DefRead,
                                              ssl_DefWrite,
                                              ssl_DefGetpeername,
                                              ssl_DefGetsockname
};

static const sslSocketOps ssl_secure_ops = { /* SSL. */
                                             ssl_SecureConnect,
                                             NULL,
                                             ssl_DefBind,
                                             ssl_DefListen,
                                             ssl_SecureShutdown,
                                             ssl_SecureClose,
                                             ssl_SecureRecv,
                                             ssl_SecureSend,
                                             ssl_SecureRead,
                                             ssl_SecureWrite,
                                             ssl_DefGetpeername,
                                             ssl_DefGetsockname
};

/*
** default settings for socket enables
*/
static sslOptions ssl_defaults = {
    .nextProtoNego = { siBuffer, NULL, 0 },
    .maxEarlyDataSize = 1 << 16,
    .recordSizeLimit = MAX_FRAGMENT_LENGTH + 1,
    .useSecurity = PR_TRUE,
    .useSocks = PR_FALSE,
    .requestCertificate = PR_FALSE,
    .requireCertificate = SSL_REQUIRE_FIRST_HANDSHAKE,
    .handshakeAsClient = PR_FALSE,
    .handshakeAsServer = PR_FALSE,
    .noCache = PR_FALSE,
    .fdx = PR_FALSE,
    .detectRollBack = PR_TRUE,
    .noLocks = PR_FALSE,
    .enableSessionTickets = PR_FALSE,
    .enableDeflate = PR_FALSE,
    .enableRenegotiation = SSL_RENEGOTIATE_REQUIRES_XTN,
    .requireSafeNegotiation = PR_FALSE,
    .enableFalseStart = PR_FALSE,
    .cbcRandomIV = PR_TRUE,
    .enableOCSPStapling = PR_FALSE,
    .enableALPN = PR_TRUE,
    .reuseServerECDHEKey = PR_TRUE,
    .enableFallbackSCSV = PR_FALSE,
    .enableServerDhe = PR_TRUE,
    .enableExtendedMS = PR_FALSE,
    .enableSignedCertTimestamps = PR_FALSE,
    .requireDHENamedGroups = PR_FALSE,
    .enable0RttData = PR_FALSE,
    .enableTls13CompatMode = PR_FALSE,
    .enableDtlsShortHeader = PR_FALSE,
    .enableHelloDowngradeCheck = PR_FALSE,
    .enableV2CompatibleHello = PR_FALSE,
    .enablePostHandshakeAuth = PR_FALSE
};

/*
 * default range of enabled SSL/TLS protocols
 */
static SSLVersionRange versions_defaults_stream = {
    SSL_LIBRARY_VERSION_TLS_1_0,
    SSL_LIBRARY_VERSION_TLS_1_2
};

static SSLVersionRange versions_defaults_datagram = {
    SSL_LIBRARY_VERSION_TLS_1_1,
    SSL_LIBRARY_VERSION_TLS_1_2
};

#define VERSIONS_DEFAULTS(variant) \
    (variant == ssl_variant_stream ? &versions_defaults_stream : &versions_defaults_datagram)
#define VERSIONS_POLICY_MIN(variant) \
    (variant == ssl_variant_stream ? NSS_TLS_VERSION_MIN_POLICY : NSS_DTLS_VERSION_MIN_POLICY)
#define VERSIONS_POLICY_MAX(variant) \
    (variant == ssl_variant_stream ? NSS_TLS_VERSION_MAX_POLICY : NSS_DTLS_VERSION_MAX_POLICY)

sslSessionIDLookupFunc ssl_sid_lookup;

static PRDescIdentity ssl_layer_id;

PRBool locksEverDisabled; /* implicitly PR_FALSE */
PRBool ssl_force_locks;   /* implicitly PR_FALSE */
int ssl_lock_readers = 1; /* default true. */
char ssl_debug;
char ssl_trace;
FILE *ssl_trace_iob;

#ifdef NSS_ALLOW_SSLKEYLOGFILE
FILE *ssl_keylog_iob;
PZLock *ssl_keylog_lock;
#endif

char lockStatus[] = "Locks are ENABLED.  ";
#define LOCKSTATUS_OFFSET 10 /* offset of ENABLED */

/* SRTP_NULL_HMAC_SHA1_80 and SRTP_NULL_HMAC_SHA1_32 are not implemented. */
static const PRUint16 srtpCiphers[] = {
    SRTP_AES128_CM_HMAC_SHA1_80,
    SRTP_AES128_CM_HMAC_SHA1_32,
    0
};

/* This list is in preference order.  Note that while some smaller groups appear
 * early in the list, smaller groups are generally ignored when iterating
 * through this list. ffdhe_custom must not appear in this list. */
#define ECGROUP(name, size, oid, assumeSupported)  \
    {                                              \
        ssl_grp_ec_##name, size, ssl_kea_ecdh,     \
            SEC_OID_SECG_EC_##oid, assumeSupported \
    }
#define FFGROUP(size)                           \
    {                                           \
        ssl_grp_ffdhe_##size, size, ssl_kea_dh, \
            SEC_OID_TLS_FFDHE_##size, PR_TRUE   \
    }

const sslNamedGroupDef ssl_named_groups[] = {
    /* Note that 256 for 25519 is a lie, but we only use it for checking bit
     * security and expect 256 bits there (not 255). */
    { ssl_grp_ec_curve25519, 256, ssl_kea_ecdh, SEC_OID_CURVE25519, PR_TRUE },
    ECGROUP(secp256r1, 256, SECP256R1, PR_TRUE),
    ECGROUP(secp384r1, 384, SECP384R1, PR_TRUE),
    ECGROUP(secp521r1, 521, SECP521R1, PR_TRUE),
    FFGROUP(2048),
    FFGROUP(3072),
    FFGROUP(4096),
    FFGROUP(6144),
    FFGROUP(8192),
    ECGROUP(secp192r1, 192, SECP192R1, PR_FALSE),
    ECGROUP(secp160r2, 160, SECP160R2, PR_FALSE),
    ECGROUP(secp160k1, 160, SECP160K1, PR_FALSE),
    ECGROUP(secp160r1, 160, SECP160R1, PR_FALSE),
    ECGROUP(sect163k1, 163, SECT163K1, PR_FALSE),
    ECGROUP(sect163r1, 163, SECT163R1, PR_FALSE),
    ECGROUP(sect163r2, 163, SECT163R2, PR_FALSE),
    ECGROUP(secp192k1, 192, SECP192K1, PR_FALSE),
    ECGROUP(sect193r1, 193, SECT193R1, PR_FALSE),
    ECGROUP(sect193r2, 193, SECT193R2, PR_FALSE),
    ECGROUP(secp224r1, 224, SECP224R1, PR_FALSE),
    ECGROUP(secp224k1, 224, SECP224K1, PR_FALSE),
    ECGROUP(sect233k1, 233, SECT233K1, PR_FALSE),
    ECGROUP(sect233r1, 233, SECT233R1, PR_FALSE),
    ECGROUP(sect239k1, 239, SECT239K1, PR_FALSE),
    ECGROUP(secp256k1, 256, SECP256K1, PR_FALSE),
    ECGROUP(sect283k1, 283, SECT283K1, PR_FALSE),
    ECGROUP(sect283r1, 283, SECT283R1, PR_FALSE),
    ECGROUP(sect409k1, 409, SECT409K1, PR_FALSE),
    ECGROUP(sect409r1, 409, SECT409R1, PR_FALSE),
    ECGROUP(sect571k1, 571, SECT571K1, PR_FALSE),
    ECGROUP(sect571r1, 571, SECT571R1, PR_FALSE),
};
PR_STATIC_ASSERT(SSL_NAMED_GROUP_COUNT == PR_ARRAY_SIZE(ssl_named_groups));

#undef ECGROUP
#undef FFGROUP

/* forward declarations. */
static sslSocket *ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant variant);
static SECStatus ssl_MakeLocks(sslSocket *ss);
static void ssl_SetDefaultsFromEnvironment(void);
static PRStatus ssl_PushIOLayer(sslSocket *ns, PRFileDesc *stack,
                                PRDescIdentity id);

/************************************************************************/

/*
** Lookup a socket structure from a file descriptor.
** Only functions called through the PRIOMethods table should use this.
** Other app-callable functions should use ssl_FindSocket.
*/
static sslSocket *
ssl_GetPrivate(PRFileDesc *fd)
{
    sslSocket *ss;

    PORT_Assert(fd != NULL);
    PORT_Assert(fd->methods->file_type == PR_DESC_LAYERED);
    PORT_Assert(fd->identity == ssl_layer_id);

    if (fd->methods->file_type != PR_DESC_LAYERED ||
        fd->identity != ssl_layer_id) {
        PORT_SetError(PR_BAD_DESCRIPTOR_ERROR);
        return NULL;
    }

    ss = (sslSocket *)fd->secret;
    /* Set ss->fd lazily. We can't rely on the value of ss->fd set by
     * ssl_PushIOLayer because another PR_PushIOLayer call will switch the
     * contents of the PRFileDesc pointed by ss->fd and the new layer.
     * See bug 807250.
     */
    ss->fd = fd;
    return ss;
}

/* This function tries to find the SSL layer in the stack.
 * It searches for the first SSL layer at or below the argument fd,
 * and failing that, it searches for the nearest SSL layer above the
 * argument fd.  It returns the private sslSocket from the found layer.
 */
sslSocket *
ssl_FindSocket(PRFileDesc *fd)
{
    PRFileDesc *layer;
    sslSocket *ss;

    PORT_Assert(fd != NULL);
    PORT_Assert(ssl_layer_id != 0);

    layer = PR_GetIdentitiesLayer(fd, ssl_layer_id);
    if (layer == NULL) {
        PORT_SetError(PR_BAD_DESCRIPTOR_ERROR);
        return NULL;
    }

    ss = (sslSocket *)layer->secret;
    /* Set ss->fd lazily. We can't rely on the value of ss->fd set by
     * ssl_PushIOLayer because another PR_PushIOLayer call will switch the
     * contents of the PRFileDesc pointed by ss->fd and the new layer.
     * See bug 807250.
     */
    ss->fd = layer;
    return ss;
}

static sslSocket *
ssl_DupSocket(sslSocket *os)
{
    sslSocket *ss;
    SECStatus rv;

    ss = ssl_NewSocket((PRBool)(!os->opt.noLocks), os->protocolVariant);
    if (!ss) {
        return NULL;
    }

    ss->opt = os->opt;
    ss->opt.useSocks = PR_FALSE;
    rv = SECITEM_CopyItem(NULL, &ss->opt.nextProtoNego, &os->opt.nextProtoNego);
    if (rv != SECSuccess) {
        goto loser;
    }
    ss->vrange = os->vrange;

    ss->peerID = !os->peerID ? NULL : PORT_Strdup(os->peerID);
    ss->url = !os->url ? NULL : PORT_Strdup(os->url);

    ss->ops = os->ops;
    ss->rTimeout = os->rTimeout;
    ss->wTimeout = os->wTimeout;
    ss->cTimeout = os->cTimeout;
    ss->dbHandle = os->dbHandle;

    /* copy ssl2&3 policy & prefs, even if it's not selected (yet) */
    PORT_Memcpy(ss->cipherSuites, os->cipherSuites, sizeof os->cipherSuites);
    PORT_Memcpy(ss->ssl3.dtlsSRTPCiphers, os->ssl3.dtlsSRTPCiphers,
                sizeof(PRUint16) * os->ssl3.dtlsSRTPCipherCount);
    ss->ssl3.dtlsSRTPCipherCount = os->ssl3.dtlsSRTPCipherCount;
    PORT_Memcpy(ss->ssl3.signatureSchemes, os->ssl3.signatureSchemes,
                sizeof(ss->ssl3.signatureSchemes[0]) *
                    os->ssl3.signatureSchemeCount);
    ss->ssl3.signatureSchemeCount = os->ssl3.signatureSchemeCount;
    ss->ssl3.downgradeCheckVersion = os->ssl3.downgradeCheckVersion;

    ss->ssl3.dheWeakGroupEnabled = os->ssl3.dheWeakGroupEnabled;

    if (ss->opt.useSecurity) {
        PRCList *cursor;

        for (cursor = PR_NEXT_LINK(&os->serverCerts);
             cursor != &os->serverCerts;
             cursor = PR_NEXT_LINK(cursor)) {
            sslServerCert *sc = ssl_CopyServerCert((sslServerCert *)cursor);
            if (!sc)
                goto loser;
            PR_APPEND_LINK(&sc->link, &ss->serverCerts);
        }

        for (cursor = PR_NEXT_LINK(&os->ephemeralKeyPairs);
             cursor != &os->ephemeralKeyPairs;
             cursor = PR_NEXT_LINK(cursor)) {
            sslEphemeralKeyPair *okp = (sslEphemeralKeyPair *)cursor;
            sslEphemeralKeyPair *skp = ssl_CopyEphemeralKeyPair(okp);
            if (!skp)
                goto loser;
            PR_APPEND_LINK(&skp->link, &ss->ephemeralKeyPairs);
        }

        for (cursor = PR_NEXT_LINK(&os->extensionHooks);
             cursor != &os->extensionHooks;
             cursor = PR_NEXT_LINK(cursor)) {
            sslCustomExtensionHooks *oh = (sslCustomExtensionHooks *)cursor;
            sslCustomExtensionHooks *sh = PORT_ZNew(sslCustomExtensionHooks);
            if (!sh) {
                goto loser;
            }
            *sh = *oh;
            PR_APPEND_LINK(&sh->link, &ss->extensionHooks);
        }

        /*
         * XXX the preceding CERT_ and SECKEY_ functions can fail and return NULL.
         * XXX We should detect this, and not just march on with NULL pointers.
         */
        ss->authCertificate = os->authCertificate;
        ss->authCertificateArg = os->authCertificateArg;
        ss->getClientAuthData = os->getClientAuthData;
        ss->getClientAuthDataArg = os->getClientAuthDataArg;
        ss->sniSocketConfig = os->sniSocketConfig;
        ss->sniSocketConfigArg = os->sniSocketConfigArg;
        ss->alertReceivedCallback = os->alertReceivedCallback;
        ss->alertReceivedCallbackArg = os->alertReceivedCallbackArg;
        ss->alertSentCallback = os->alertSentCallback;
        ss->alertSentCallbackArg = os->alertSentCallbackArg;
        ss->handleBadCert = os->handleBadCert;
        ss->badCertArg = os->badCertArg;
        ss->handshakeCallback = os->handshakeCallback;
        ss->handshakeCallbackData = os->handshakeCallbackData;
        ss->canFalseStartCallback = os->canFalseStartCallback;
        ss->canFalseStartCallbackData = os->canFalseStartCallbackData;
        ss->pkcs11PinArg = os->pkcs11PinArg;
        ss->nextProtoCallback = os->nextProtoCallback;
        ss->nextProtoArg = os->nextProtoArg;
        PORT_Memcpy((void *)ss->namedGroupPreferences,
                    os->namedGroupPreferences,
                    sizeof(ss->namedGroupPreferences));
        ss->additionalShares = os->additionalShares;
        ss->resumptionTokenCallback = os->resumptionTokenCallback;
        ss->resumptionTokenContext = os->resumptionTokenContext;

        if (os->esniKeys) {
            ss->esniKeys = tls13_CopyESNIKeys(os->esniKeys);
            if (!ss->esniKeys) {
                goto loser;
            }
        }

        /* Create security data */
        rv = ssl_CopySecurityInfo(ss, os);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    return ss;

loser:
    ssl_FreeSocket(ss);
    return NULL;
}

static void
ssl_DestroyLocks(sslSocket *ss)
{
    /* Destroy locks. */
    if (ss->firstHandshakeLock) {
        PZ_DestroyMonitor(ss->firstHandshakeLock);
        ss->firstHandshakeLock = NULL;
    }
    if (ss->ssl3HandshakeLock) {
        PZ_DestroyMonitor(ss->ssl3HandshakeLock);
        ss->ssl3HandshakeLock = NULL;
    }
    if (ss->specLock) {
        NSSRWLock_Destroy(ss->specLock);
        ss->specLock = NULL;
    }

    if (ss->recvLock) {
        PZ_DestroyLock(ss->recvLock);
        ss->recvLock = NULL;
    }
    if (ss->sendLock) {
        PZ_DestroyLock(ss->sendLock);
        ss->sendLock = NULL;
    }
    if (ss->xmitBufLock) {
        PZ_DestroyMonitor(ss->xmitBufLock);
        ss->xmitBufLock = NULL;
    }
    if (ss->recvBufLock) {
        PZ_DestroyMonitor(ss->recvBufLock);
        ss->recvBufLock = NULL;
    }
}

/* Caller holds any relevant locks */
static void
ssl_DestroySocketContents(sslSocket *ss)
{
    PRCList *cursor;

    /* Free up socket */
    ssl_DestroySecurityInfo(&ss->sec);

    ssl3_DestroySSL3Info(ss);

    PORT_Free(ss->saveBuf.buf);
    PORT_Free(ss->pendingBuf.buf);
    ssl3_DestroyGather(&ss->gs);

    if (ss->peerID != NULL)
        PORT_Free(ss->peerID);
    if (ss->url != NULL)
        PORT_Free((void *)ss->url); /* CONST */

    /* Clean up server certificates and sundries. */
    while (!PR_CLIST_IS_EMPTY(&ss->serverCerts)) {
        cursor = PR_LIST_TAIL(&ss->serverCerts);
        PR_REMOVE_LINK(cursor);
        ssl_FreeServerCert((sslServerCert *)cursor);
    }

    /* Remove extension handlers. */
    ssl_ClearPRCList(&ss->extensionHooks, NULL);

    ssl_FreeEphemeralKeyPairs(ss);
    SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
    ssl3_FreeSniNameArray(&ss->xtnData);

    ssl_ClearPRCList(&ss->ssl3.hs.dtlsSentHandshake, NULL);
    ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);

    tls13_DestroyESNIKeys(ss->esniKeys);
}

/*
 * free an sslSocket struct, and all the stuff that hangs off of it
 */
void
ssl_FreeSocket(sslSocket *ss)
{
    /* Get every lock you can imagine!
    ** Caller already holds these:
    **  SSL_LOCK_READER(ss);
    **  SSL_LOCK_WRITER(ss);
    */
    ssl_Get1stHandshakeLock(ss);
    ssl_GetRecvBufLock(ss);
    ssl_GetSSL3HandshakeLock(ss);
    ssl_GetXmitBufLock(ss);
    ssl_GetSpecWriteLock(ss);

    ssl_DestroySocketContents(ss);

    /* Release all the locks acquired above.  */
    SSL_UNLOCK_READER(ss);
    SSL_UNLOCK_WRITER(ss);
    ssl_Release1stHandshakeLock(ss);
    ssl_ReleaseRecvBufLock(ss);
    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_ReleaseXmitBufLock(ss);
    ssl_ReleaseSpecWriteLock(ss);

    ssl_DestroyLocks(ss);

#ifdef DEBUG
    PORT_Memset(ss, 0x1f, sizeof *ss);
#endif
    PORT_Free(ss);
    return;
}

/************************************************************************/
SECStatus
ssl_EnableNagleDelay(sslSocket *ss, PRBool enabled)
{
    PRFileDesc *osfd = ss->fd->lower;
    SECStatus rv = SECFailure;
    PRSocketOptionData opt;

    opt.option = PR_SockOpt_NoDelay;
    opt.value.no_delay = (PRBool)!enabled;

    if (osfd->methods->setsocketoption) {
        rv = (SECStatus)osfd->methods->setsocketoption(osfd, &opt);
    } else {
        PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    }

    return rv;
}

static void
ssl_ChooseOps(sslSocket *ss)
{
    ss->ops = ss->opt.useSecurity ? &ssl_secure_ops : &ssl_default_ops;
}

/* Called from SSL_Enable (immediately below) */
static SECStatus
PrepareSocket(sslSocket *ss)
{
    SECStatus rv = SECSuccess;

    ssl_ChooseOps(ss);
    return rv;
}

SECStatus
SSL_Enable(PRFileDesc *fd, int which, PRIntn on)
{
    return SSL_OptionSet(fd, which, on);
}

static PRBool ssl_VersionIsSupportedByPolicy(
    SSLProtocolVariant protocolVariant, SSL3ProtocolVersion version);

/* Implements the semantics for SSL_OptionSet(SSL_ENABLE_TLS, on) described in
 * ssl.h in the section "SSL version range setting API".
 */
static void
ssl_EnableTLS(SSLVersionRange *vrange, PRIntn enable)
{
    if (enable) {
        /* don't turn it on if tls1.0 disallowed by by policy */
        if (!ssl_VersionIsSupportedByPolicy(ssl_variant_stream,
                                            SSL_LIBRARY_VERSION_TLS_1_0)) {
            return;
        }
    }
    if (SSL_ALL_VERSIONS_DISABLED(vrange)) {
        if (enable) {
            vrange->min = SSL_LIBRARY_VERSION_TLS_1_0;
            vrange->max = SSL_LIBRARY_VERSION_TLS_1_0;
        } /* else don't change anything */
        return;
    }

    if (enable) {
        /* Expand the range of enabled version to include TLS 1.0 */
        vrange->min = PR_MIN(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
        vrange->max = PR_MAX(vrange->max, SSL_LIBRARY_VERSION_TLS_1_0);
    } else {
        /* Disable all TLS versions, leaving only SSL 3.0 if it was enabled */
        if (vrange->min == SSL_LIBRARY_VERSION_3_0) {
            vrange->max = SSL_LIBRARY_VERSION_3_0;
        } else {
            /* Only TLS was enabled, so now no versions are. */
            vrange->min = SSL_LIBRARY_VERSION_NONE;
            vrange->max = SSL_LIBRARY_VERSION_NONE;
        }
    }
}

/* Implements the semantics for SSL_OptionSet(SSL_ENABLE_SSL3, on) described in
 * ssl.h in the section "SSL version range setting API".
 */
static void
ssl_EnableSSL3(SSLVersionRange *vrange, PRIntn enable)
{
    if (enable) {
        /* don't turn it on if ssl3 disallowed by by policy */
        if (!ssl_VersionIsSupportedByPolicy(ssl_variant_stream,
                                            SSL_LIBRARY_VERSION_3_0)) {
            return;
        }
    }
    if (SSL_ALL_VERSIONS_DISABLED(vrange)) {
        if (enable) {
            vrange->min = SSL_LIBRARY_VERSION_3_0;
            vrange->max = SSL_LIBRARY_VERSION_3_0;
        } /* else don't change anything */
        return;
    }

    if (enable) {
        /* Expand the range of enabled versions to include SSL 3.0. We know
         * SSL 3.0 or some version of TLS is already enabled at this point, so
         * we don't need to change vrange->max.
         */
        vrange->min = SSL_LIBRARY_VERSION_3_0;
    } else {
        /* Disable SSL 3.0, leaving TLS unaffected. */
        if (vrange->max > SSL_LIBRARY_VERSION_3_0) {
            vrange->min = PR_MAX(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
        } else {
            /* Only SSL 3.0 was enabled, so now no versions are. */
            vrange->min = SSL_LIBRARY_VERSION_NONE;
            vrange->max = SSL_LIBRARY_VERSION_NONE;
        }
    }
}

SECStatus
SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRIntn val)
{
    sslSocket *ss = ssl_FindSocket(fd);
    SECStatus rv = SECSuccess;
    PRBool holdingLocks;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in Enable", SSL_GETPID(), fd));
        return SECFailure;
    }

    holdingLocks = (!ss->opt.noLocks);
    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    switch (which) {
        case SSL_SOCKS:
            ss->opt.useSocks = PR_FALSE;
            rv = PrepareSocket(ss);
            if (val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
            }
            break;

        case SSL_SECURITY:
            ss->opt.useSecurity = val;
            rv = PrepareSocket(ss);
            break;

        case SSL_REQUEST_CERTIFICATE:
            ss->opt.requestCertificate = val;
            break;

        case SSL_REQUIRE_CERTIFICATE:
            ss->opt.requireCertificate = val;
            break;

        case SSL_HANDSHAKE_AS_CLIENT:
            if (ss->opt.handshakeAsServer && val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
                break;
            }
            ss->opt.handshakeAsClient = val;
            break;

        case SSL_HANDSHAKE_AS_SERVER:
            if (ss->opt.handshakeAsClient && val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
                break;
            }
            ss->opt.handshakeAsServer = val;
            break;

        case SSL_ENABLE_TLS:
            if (IS_DTLS(ss)) {
                if (val) {
                    PORT_SetError(SEC_ERROR_INVALID_ARGS);
                    rv = SECFailure; /* not allowed */
                }
                break;
            }
            ssl_EnableTLS(&ss->vrange, val);
            break;

        case SSL_ENABLE_SSL3:
            if (IS_DTLS(ss)) {
                if (val) {
                    PORT_SetError(SEC_ERROR_INVALID_ARGS);
                    rv = SECFailure; /* not allowed */
                }
                break;
            }
            ssl_EnableSSL3(&ss->vrange, val);
            break;

        case SSL_ENABLE_SSL2:
        case SSL_V2_COMPATIBLE_HELLO:
            /* We no longer support SSL v2.
             * However, if an old application requests to disable SSL v2,
             * we shouldn't fail.
             */
            if (val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
            }
            break;

        case SSL_NO_CACHE:
            ss->opt.noCache = val;
            break;

        case SSL_ENABLE_FDX:
            if (val && ss->opt.noLocks) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
            }
            ss->opt.fdx = val;
            break;

        case SSL_ROLLBACK_DETECTION:
            ss->opt.detectRollBack = val;
            break;

        case SSL_NO_STEP_DOWN:
            break;

        case SSL_BYPASS_PKCS11:
            break;

        case SSL_NO_LOCKS:
            if (val && ss->opt.fdx) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
            }
            if (val && ssl_force_locks)
                val = PR_FALSE; /* silent override */
            ss->opt.noLocks = val;
            if (val) {
                locksEverDisabled = PR_TRUE;
                strcpy(lockStatus + LOCKSTATUS_OFFSET, "DISABLED.");
            } else if (!holdingLocks) {
                rv = ssl_MakeLocks(ss);
                if (rv != SECSuccess) {
                    ss->opt.noLocks = PR_TRUE;
                }
            }
            break;

        case SSL_ENABLE_SESSION_TICKETS:
            ss->opt.enableSessionTickets = val;
            break;

        case SSL_ENABLE_DEFLATE:
            ss->opt.enableDeflate = val;
            break;

        case SSL_ENABLE_RENEGOTIATION:
            if (IS_DTLS(ss) && val != SSL_RENEGOTIATE_NEVER) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
                break;
            }
            ss->opt.enableRenegotiation = val;
            break;

        case SSL_REQUIRE_SAFE_NEGOTIATION:
            ss->opt.requireSafeNegotiation = val;
            break;

        case SSL_ENABLE_FALSE_START:
            ss->opt.enableFalseStart = val;
            break;

        case SSL_CBC_RANDOM_IV:
            ss->opt.cbcRandomIV = val;
            break;

        case SSL_ENABLE_OCSP_STAPLING:
            ss->opt.enableOCSPStapling = val;
            break;

        case SSL_ENABLE_NPN:
            break;

        case SSL_ENABLE_ALPN:
            ss->opt.enableALPN = val;
            break;

        case SSL_REUSE_SERVER_ECDHE_KEY:
            ss->opt.reuseServerECDHEKey = val;
            break;

        case SSL_ENABLE_FALLBACK_SCSV:
            ss->opt.enableFallbackSCSV = val;
            break;

        case SSL_ENABLE_SERVER_DHE:
            ss->opt.enableServerDhe = val;
            break;

        case SSL_ENABLE_EXTENDED_MASTER_SECRET:
            ss->opt.enableExtendedMS = val;
            break;

        case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
            ss->opt.enableSignedCertTimestamps = val;
            break;

        case SSL_REQUIRE_DH_NAMED_GROUPS:
            ss->opt.requireDHENamedGroups = val;
            break;

        case SSL_ENABLE_0RTT_DATA:
            ss->opt.enable0RttData = val;
            break;

        case SSL_RECORD_SIZE_LIMIT:
            if (val < 64 || val > (MAX_FRAGMENT_LENGTH + 1)) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                rv = SECFailure;
            } else {
                ss->opt.recordSizeLimit = val;
            }
            break;

        case SSL_ENABLE_TLS13_COMPAT_MODE:
            ss->opt.enableTls13CompatMode = val;
            break;

        case SSL_ENABLE_DTLS_SHORT_HEADER:
            ss->opt.enableDtlsShortHeader = val;
            break;

        case SSL_ENABLE_HELLO_DOWNGRADE_CHECK:
            ss->opt.enableHelloDowngradeCheck = val;
            break;

        case SSL_ENABLE_V2_COMPATIBLE_HELLO:
            ss->opt.enableV2CompatibleHello = val;
            break;

        case SSL_ENABLE_POST_HANDSHAKE_AUTH:
            ss->opt.enablePostHandshakeAuth = val;
            break;

        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            rv = SECFailure;
    }

    /* We can't use the macros for releasing the locks here,
     * because ss->opt.noLocks might have changed just above.
     * We must release these locks (monitors) here, if we aquired them above,
     * regardless of the current value of ss->opt.noLocks.
     */
    if (holdingLocks) {
        PZ_ExitMonitor((ss)->ssl3HandshakeLock);
        PZ_ExitMonitor((ss)->firstHandshakeLock);
    }

    return rv;
}

SECStatus
SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRIntn *pVal)
{
    sslSocket *ss = ssl_FindSocket(fd);
    SECStatus rv = SECSuccess;
    PRIntn val = PR_FALSE;

    if (!pVal) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in Enable", SSL_GETPID(), fd));
        *pVal = PR_FALSE;
        return SECFailure;
    }

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    switch (which) {
        case SSL_SOCKS:
            val = PR_FALSE;
            break;
        case SSL_SECURITY:
            val = ss->opt.useSecurity;
            break;
        case SSL_REQUEST_CERTIFICATE:
            val = ss->opt.requestCertificate;
            break;
        case SSL_REQUIRE_CERTIFICATE:
            val = ss->opt.requireCertificate;
            break;
        case SSL_HANDSHAKE_AS_CLIENT:
            val = ss->opt.handshakeAsClient;
            break;
        case SSL_HANDSHAKE_AS_SERVER:
            val = ss->opt.handshakeAsServer;
            break;
        case SSL_ENABLE_TLS:
            val = ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_0;
            break;
        case SSL_ENABLE_SSL3:
            val = ss->vrange.min == SSL_LIBRARY_VERSION_3_0;
            break;
        case SSL_ENABLE_SSL2:
        case SSL_V2_COMPATIBLE_HELLO:
            val = PR_FALSE;
            break;
        case SSL_NO_CACHE:
            val = ss->opt.noCache;
            break;
        case SSL_ENABLE_FDX:
            val = ss->opt.fdx;
            break;
        case SSL_ROLLBACK_DETECTION:
            val = ss->opt.detectRollBack;
            break;
        case SSL_NO_STEP_DOWN:
            val = PR_FALSE;
            break;
        case SSL_BYPASS_PKCS11:
            val = PR_FALSE;
            break;
        case SSL_NO_LOCKS:
            val = ss->opt.noLocks;
            break;
        case SSL_ENABLE_SESSION_TICKETS:
            val = ss->opt.enableSessionTickets;
            break;
        case SSL_ENABLE_DEFLATE:
            val = ss->opt.enableDeflate;
            break;
        case SSL_ENABLE_RENEGOTIATION:
            val = ss->opt.enableRenegotiation;
            break;
        case SSL_REQUIRE_SAFE_NEGOTIATION:
            val = ss->opt.requireSafeNegotiation;
            break;
        case SSL_ENABLE_FALSE_START:
            val = ss->opt.enableFalseStart;
            break;
        case SSL_CBC_RANDOM_IV:
            val = ss->opt.cbcRandomIV;
            break;
        case SSL_ENABLE_OCSP_STAPLING:
            val = ss->opt.enableOCSPStapling;
            break;
        case SSL_ENABLE_NPN:
            val = PR_FALSE;
            break;
        case SSL_ENABLE_ALPN:
            val = ss->opt.enableALPN;
            break;
        case SSL_REUSE_SERVER_ECDHE_KEY:
            val = ss->opt.reuseServerECDHEKey;
            break;
        case SSL_ENABLE_FALLBACK_SCSV:
            val = ss->opt.enableFallbackSCSV;
            break;
        case SSL_ENABLE_SERVER_DHE:
            val = ss->opt.enableServerDhe;
            break;
        case SSL_ENABLE_EXTENDED_MASTER_SECRET:
            val = ss->opt.enableExtendedMS;
            break;
        case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
            val = ss->opt.enableSignedCertTimestamps;
            break;
        case SSL_REQUIRE_DH_NAMED_GROUPS:
            val = ss->opt.requireDHENamedGroups;
            break;
        case SSL_ENABLE_0RTT_DATA:
            val = ss->opt.enable0RttData;
            break;
        case SSL_RECORD_SIZE_LIMIT:
            val = ss->opt.recordSizeLimit;
            break;
        case SSL_ENABLE_TLS13_COMPAT_MODE:
            val = ss->opt.enableTls13CompatMode;
            break;
        case SSL_ENABLE_DTLS_SHORT_HEADER:
            val = ss->opt.enableDtlsShortHeader;
            break;
        case SSL_ENABLE_HELLO_DOWNGRADE_CHECK:
            val = ss->opt.enableHelloDowngradeCheck;
            break;
        case SSL_ENABLE_V2_COMPATIBLE_HELLO:
            val = ss->opt.enableV2CompatibleHello;
            break;
        case SSL_ENABLE_POST_HANDSHAKE_AUTH:
            val = ss->opt.enablePostHandshakeAuth;
            break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            rv = SECFailure;
    }

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    *pVal = val;
    return rv;
}

SECStatus
SSL_OptionGetDefault(PRInt32 which, PRIntn *pVal)
{
    SECStatus rv = SECSuccess;
    PRIntn val = PR_FALSE;

    if (!pVal) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ssl_SetDefaultsFromEnvironment();

    switch (which) {
        case SSL_SOCKS:
            val = PR_FALSE;
            break;
        case SSL_SECURITY:
            val = ssl_defaults.useSecurity;
            break;
        case SSL_REQUEST_CERTIFICATE:
            val = ssl_defaults.requestCertificate;
            break;
        case SSL_REQUIRE_CERTIFICATE:
            val = ssl_defaults.requireCertificate;
            break;
        case SSL_HANDSHAKE_AS_CLIENT:
            val = ssl_defaults.handshakeAsClient;
            break;
        case SSL_HANDSHAKE_AS_SERVER:
            val = ssl_defaults.handshakeAsServer;
            break;
        case SSL_ENABLE_TLS:
            val = versions_defaults_stream.max >= SSL_LIBRARY_VERSION_TLS_1_0;
            break;
        case SSL_ENABLE_SSL3:
            val = versions_defaults_stream.min == SSL_LIBRARY_VERSION_3_0;
            break;
        case SSL_ENABLE_SSL2:
        case SSL_V2_COMPATIBLE_HELLO:
            val = PR_FALSE;
            break;
        case SSL_NO_CACHE:
            val = ssl_defaults.noCache;
            break;
        case SSL_ENABLE_FDX:
            val = ssl_defaults.fdx;
            break;
        case SSL_ROLLBACK_DETECTION:
            val = ssl_defaults.detectRollBack;
            break;
        case SSL_NO_STEP_DOWN:
            val = PR_FALSE;
            break;
        case SSL_BYPASS_PKCS11:
            val = PR_FALSE;
            break;
        case SSL_NO_LOCKS:
            val = ssl_defaults.noLocks;
            break;
        case SSL_ENABLE_SESSION_TICKETS:
            val = ssl_defaults.enableSessionTickets;
            break;
        case SSL_ENABLE_DEFLATE:
            val = ssl_defaults.enableDeflate;
            break;
        case SSL_ENABLE_RENEGOTIATION:
            val = ssl_defaults.enableRenegotiation;
            break;
        case SSL_REQUIRE_SAFE_NEGOTIATION:
            val = ssl_defaults.requireSafeNegotiation;
            break;
        case SSL_ENABLE_FALSE_START:
            val = ssl_defaults.enableFalseStart;
            break;
        case SSL_CBC_RANDOM_IV:
            val = ssl_defaults.cbcRandomIV;
            break;
        case SSL_ENABLE_OCSP_STAPLING:
            val = ssl_defaults.enableOCSPStapling;
            break;
        case SSL_ENABLE_NPN:
            val = PR_FALSE;
            break;
        case SSL_ENABLE_ALPN:
            val = ssl_defaults.enableALPN;
            break;
        case SSL_REUSE_SERVER_ECDHE_KEY:
            val = ssl_defaults.reuseServerECDHEKey;
            break;
        case SSL_ENABLE_FALLBACK_SCSV:
            val = ssl_defaults.enableFallbackSCSV;
            break;
        case SSL_ENABLE_SERVER_DHE:
            val = ssl_defaults.enableServerDhe;
            break;
        case SSL_ENABLE_EXTENDED_MASTER_SECRET:
            val = ssl_defaults.enableExtendedMS;
            break;
        case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
            val = ssl_defaults.enableSignedCertTimestamps;
            break;
        case SSL_ENABLE_0RTT_DATA:
            val = ssl_defaults.enable0RttData;
            break;
        case SSL_RECORD_SIZE_LIMIT:
            val = ssl_defaults.recordSizeLimit;
            break;
        case SSL_ENABLE_TLS13_COMPAT_MODE:
            val = ssl_defaults.enableTls13CompatMode;
            break;
        case SSL_ENABLE_DTLS_SHORT_HEADER:
            val = ssl_defaults.enableDtlsShortHeader;
            break;
        case SSL_ENABLE_HELLO_DOWNGRADE_CHECK:
            val = ssl_defaults.enableHelloDowngradeCheck;
            break;
        case SSL_ENABLE_V2_COMPATIBLE_HELLO:
            val = ssl_defaults.enableV2CompatibleHello;
            break;
        case SSL_ENABLE_POST_HANDSHAKE_AUTH:
            val = ssl_defaults.enablePostHandshakeAuth;
            break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            rv = SECFailure;
    }

    *pVal = val;
    return rv;
}

/* XXX Use Global Lock to protect this stuff. */
SECStatus
SSL_EnableDefault(int which, PRIntn val)
{
    return SSL_OptionSetDefault(which, val);
}

SECStatus
SSL_OptionSetDefault(PRInt32 which, PRIntn val)
{
    SECStatus status = ssl_Init();

    if (status != SECSuccess) {
        return status;
    }

    ssl_SetDefaultsFromEnvironment();

    switch (which) {
        case SSL_SOCKS:
            ssl_defaults.useSocks = PR_FALSE;
            if (val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            break;

        case SSL_SECURITY:
            ssl_defaults.useSecurity = val;
            break;

        case SSL_REQUEST_CERTIFICATE:
            ssl_defaults.requestCertificate = val;
            break;

        case SSL_REQUIRE_CERTIFICATE:
            ssl_defaults.requireCertificate = val;
            break;

        case SSL_HANDSHAKE_AS_CLIENT:
            if (ssl_defaults.handshakeAsServer && val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            ssl_defaults.handshakeAsClient = val;
            break;

        case SSL_HANDSHAKE_AS_SERVER:
            if (ssl_defaults.handshakeAsClient && val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            ssl_defaults.handshakeAsServer = val;
            break;

        case SSL_ENABLE_TLS:
            ssl_EnableTLS(&versions_defaults_stream, val);
            break;

        case SSL_ENABLE_SSL3:
            ssl_EnableSSL3(&versions_defaults_stream, val);
            break;

        case SSL_ENABLE_SSL2:
        case SSL_V2_COMPATIBLE_HELLO:
            /* We no longer support SSL v2.
             * However, if an old application requests to disable SSL v2,
             * we shouldn't fail.
             */
            if (val) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            break;

        case SSL_NO_CACHE:
            ssl_defaults.noCache = val;
            break;

        case SSL_ENABLE_FDX:
            if (val && ssl_defaults.noLocks) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            ssl_defaults.fdx = val;
            break;

        case SSL_ROLLBACK_DETECTION:
            ssl_defaults.detectRollBack = val;
            break;

        case SSL_NO_STEP_DOWN:
            break;

        case SSL_BYPASS_PKCS11:
            break;

        case SSL_NO_LOCKS:
            if (val && ssl_defaults.fdx) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            if (val && ssl_force_locks)
                val = PR_FALSE; /* silent override */
            ssl_defaults.noLocks = val;
            if (val) {
                locksEverDisabled = PR_TRUE;
                strcpy(lockStatus + LOCKSTATUS_OFFSET, "DISABLED.");
            }
            break;

        case SSL_ENABLE_SESSION_TICKETS:
            ssl_defaults.enableSessionTickets = val;
            break;

        case SSL_ENABLE_DEFLATE:
            ssl_defaults.enableDeflate = val;
            break;

        case SSL_ENABLE_RENEGOTIATION:
            ssl_defaults.enableRenegotiation = val;
            break;

        case SSL_REQUIRE_SAFE_NEGOTIATION:
            ssl_defaults.requireSafeNegotiation = val;
            break;

        case SSL_ENABLE_FALSE_START:
            ssl_defaults.enableFalseStart = val;
            break;

        case SSL_CBC_RANDOM_IV:
            ssl_defaults.cbcRandomIV = val;
            break;

        case SSL_ENABLE_OCSP_STAPLING:
            ssl_defaults.enableOCSPStapling = val;
            break;

        case SSL_ENABLE_NPN:
            break;

        case SSL_ENABLE_ALPN:
            ssl_defaults.enableALPN = val;
            break;

        case SSL_REUSE_SERVER_ECDHE_KEY:
            ssl_defaults.reuseServerECDHEKey = val;
            break;

        case SSL_ENABLE_FALLBACK_SCSV:
            ssl_defaults.enableFallbackSCSV = val;
            break;

        case SSL_ENABLE_SERVER_DHE:
            ssl_defaults.enableServerDhe = val;
            break;

        case SSL_ENABLE_EXTENDED_MASTER_SECRET:
            ssl_defaults.enableExtendedMS = val;
            break;

        case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
            ssl_defaults.enableSignedCertTimestamps = val;
            break;

        case SSL_ENABLE_0RTT_DATA:
            ssl_defaults.enable0RttData = val;
            break;

        case SSL_RECORD_SIZE_LIMIT:
            if (val < 64 || val > (MAX_FRAGMENT_LENGTH + 1)) {
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
            }
            ssl_defaults.recordSizeLimit = val;
            break;

        case SSL_ENABLE_TLS13_COMPAT_MODE:
            ssl_defaults.enableTls13CompatMode = val;
            break;

        case SSL_ENABLE_DTLS_SHORT_HEADER:
            ssl_defaults.enableDtlsShortHeader = val;
            break;

        case SSL_ENABLE_HELLO_DOWNGRADE_CHECK:
            ssl_defaults.enableHelloDowngradeCheck = val;
            break;

        case SSL_ENABLE_V2_COMPATIBLE_HELLO:
            ssl_defaults.enableV2CompatibleHello = val;
            break;

        case SSL_ENABLE_POST_HANDSHAKE_AUTH:
            ssl_defaults.enablePostHandshakeAuth = val;
            break;

        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return SECFailure;
    }
    return SECSuccess;
}

SECStatus
SSLExp_SetMaxEarlyDataSize(PRFileDesc *fd, PRUint32 size)
{
    sslSocket *ss = ssl_FindSocket(fd);
    if (!ss) {
        return SECFailure; /* Error code already set. */
    }

    ss->opt.maxEarlyDataSize = size;
    return SECSuccess;
}

/* function tells us if the cipher suite is one that we no longer support. */
static PRBool
ssl_IsRemovedCipherSuite(PRInt32 suite)
{
    switch (suite) {
        case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
        case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA:
        case SSL_FORTEZZA_DMS_WITH_RC4_128_SHA:
            return PR_TRUE;
        default:
            return PR_FALSE;
    }
}

/* Part of the public NSS API.
 * Since this is a global (not per-socket) setting, we cannot use the
 * HandshakeLock to protect this.  Probably want a global lock.
 */
SECStatus
SSL_SetPolicy(long which, int policy)
{
    if (ssl_IsRemovedCipherSuite(which))
        return SECSuccess;
    return SSL_CipherPolicySet(which, policy);
}

SECStatus
ssl_CipherPolicySet(PRInt32 which, PRInt32 policy)
{
    SECStatus rv = SECSuccess;

    if (ssl_IsRemovedCipherSuite(which)) {
        rv = SECSuccess;
    } else {
        rv = ssl3_SetPolicy((ssl3CipherSuite)which, policy);
    }
    return rv;
}
SECStatus
SSL_CipherPolicySet(PRInt32 which, PRInt32 policy)
{
    SECStatus rv = ssl_Init();

    if (rv != SECSuccess) {
        return rv;
    }
    return ssl_CipherPolicySet(which, policy);
}

SECStatus
SSL_CipherPolicyGet(PRInt32 which, PRInt32 *oPolicy)
{
    SECStatus rv;

    if (!oPolicy) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (ssl_IsRemovedCipherSuite(which)) {
        *oPolicy = SSL_NOT_ALLOWED;
        rv = SECSuccess;
    } else {
        rv = ssl3_GetPolicy((ssl3CipherSuite)which, oPolicy);
    }
    return rv;
}

/* Part of the public NSS API.
 * Since this is a global (not per-socket) setting, we cannot use the
 * HandshakeLock to protect this.  Probably want a global lock.
 * These changes have no effect on any sslSockets already created.
 */
SECStatus
SSL_EnableCipher(long which, PRBool enabled)
{
    if (ssl_IsRemovedCipherSuite(which))
        return SECSuccess;
    return SSL_CipherPrefSetDefault(which, enabled);
}

SECStatus
ssl_CipherPrefSetDefault(PRInt32 which, PRBool enabled)
{
    if (ssl_IsRemovedCipherSuite(which))
        return SECSuccess;
    return ssl3_CipherPrefSetDefault((ssl3CipherSuite)which, enabled);
}

SECStatus
SSL_CipherPrefSetDefault(PRInt32 which, PRBool enabled)
{
    SECStatus rv = ssl_Init();

    if (rv != SECSuccess) {
        return rv;
    }
    return ssl_CipherPrefSetDefault(which, enabled);
}

SECStatus
SSL_CipherPrefGetDefault(PRInt32 which, PRBool *enabled)
{
    SECStatus rv;

    if (!enabled) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (ssl_IsRemovedCipherSuite(which)) {
        *enabled = PR_FALSE;
        rv = SECSuccess;
    } else {
        rv = ssl3_CipherPrefGetDefault((ssl3CipherSuite)which, enabled);
    }
    return rv;
}

SECStatus
SSL_CipherPrefSet(PRFileDesc *fd, PRInt32 which, PRBool enabled)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in CipherPrefSet", SSL_GETPID(), fd));
        return SECFailure;
    }
    if (ssl_IsRemovedCipherSuite(which))
        return SECSuccess;
    return ssl3_CipherPrefSet(ss, (ssl3CipherSuite)which, enabled);
}

SECStatus
SSL_CipherPrefGet(PRFileDesc *fd, PRInt32 which, PRBool *enabled)
{
    SECStatus rv;
    sslSocket *ss = ssl_FindSocket(fd);

    if (!enabled) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in CipherPrefGet", SSL_GETPID(), fd));
        *enabled = PR_FALSE;
        return SECFailure;
    }
    if (ssl_IsRemovedCipherSuite(which)) {
        *enabled = PR_FALSE;
        rv = SECSuccess;
    } else {
        rv = ssl3_CipherPrefGet(ss, (ssl3CipherSuite)which, enabled);
    }
    return rv;
}

SECStatus
NSS_SetDomesticPolicy(void)
{
    SECStatus status = SECSuccess;
    const PRUint16 *cipher;
    SECStatus rv;
    PRUint32 policy;

    /* If we've already defined some policy oids, skip changing them */
    rv = NSS_GetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY, &policy);
    if ((rv == SECSuccess) && (policy & NSS_USE_POLICY_IN_SSL)) {
        return ssl_Init(); /* make sure the policies have bee loaded */
    }

    for (cipher = SSL_ImplementedCiphers; *cipher != 0; ++cipher) {
        status = SSL_SetPolicy(*cipher, SSL_ALLOWED);
        if (status != SECSuccess)
            break;
    }
    return status;
}

SECStatus
NSS_SetExportPolicy(void)
{
    return NSS_SetDomesticPolicy();
}

SECStatus
NSS_SetFrancePolicy(void)
{
    return NSS_SetDomesticPolicy();
}

SECStatus
SSL_NamedGroupConfig(PRFileDesc *fd, const SSLNamedGroup *groups,
                     unsigned int numGroups)
{
    unsigned int i;
    unsigned int j = 0;
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return SECFailure;
    }

    if (!groups) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (numGroups > SSL_NAMED_GROUP_COUNT) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    memset((void *)ss->namedGroupPreferences, 0,
           sizeof(ss->namedGroupPreferences));
    for (i = 0; i < numGroups; ++i) {
        const sslNamedGroupDef *groupDef = ssl_LookupNamedGroup(groups[i]);
        if (!ssl_NamedGroupEnabled(ss, groupDef)) {
            ss->namedGroupPreferences[j++] = groupDef;
        }
    }

    return SECSuccess;
}

SECStatus
SSL_DHEGroupPrefSet(PRFileDesc *fd, const SSLDHEGroupType *groups,
                    PRUint16 num_groups)
{
    sslSocket *ss;
    const SSLDHEGroupType *list;
    unsigned int count;
    int i, k, j;
    const sslNamedGroupDef *enabled[SSL_NAMED_GROUP_COUNT] = { 0 };
    static const SSLDHEGroupType default_dhe_groups[] = {
        ssl_ff_dhe_2048_group
    };

    if ((num_groups && !groups) || (!num_groups && groups) ||
        num_groups > SSL_NAMED_GROUP_COUNT) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_DHEGroupPrefSet", SSL_GETPID(), fd));
        return SECFailure;
    }

    if (groups) {
        list = groups;
        count = num_groups;
    } else {
        list = default_dhe_groups;
        count = PR_ARRAY_SIZE(default_dhe_groups);
    }

    /* save enabled ec groups and clear ss->namedGroupPreferences */
    k = 0;
    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        if (ss->namedGroupPreferences[i] &&
            ss->namedGroupPreferences[i]->keaType != ssl_kea_dh) {
            enabled[k++] = ss->namedGroupPreferences[i];
        }
        ss->namedGroupPreferences[i] = NULL;
    }

    ss->ssl3.dhePreferredGroup = NULL;
    for (i = 0; i < count; ++i) {
        PRBool duplicate = PR_FALSE;
        SSLNamedGroup name;
        const sslNamedGroupDef *groupDef;
        switch (list[i]) {
            case ssl_ff_dhe_2048_group:
                name = ssl_grp_ffdhe_2048;
                break;
            case ssl_ff_dhe_3072_group:
                name = ssl_grp_ffdhe_3072;
                break;
            case ssl_ff_dhe_4096_group:
                name = ssl_grp_ffdhe_4096;
                break;
            case ssl_ff_dhe_6144_group:
                name = ssl_grp_ffdhe_6144;
                break;
            case ssl_ff_dhe_8192_group:
                name = ssl_grp_ffdhe_8192;
                break;
            default:
                PORT_SetError(SEC_ERROR_INVALID_ARGS);
                return SECFailure;
        }
        groupDef = ssl_LookupNamedGroup(name);
        PORT_Assert(groupDef);
        if (!ss->ssl3.dhePreferredGroup) {
            ss->ssl3.dhePreferredGroup = groupDef;
        }
        PORT_Assert(k < SSL_NAMED_GROUP_COUNT);
        for (j = 0; j < k; ++j) {
            /* skip duplicates */
            if (enabled[j] == groupDef) {
                duplicate = PR_TRUE;
                break;
            }
        }
        if (!duplicate) {
            enabled[k++] = groupDef;
        }
    }
    for (i = 0; i < k; ++i) {
        ss->namedGroupPreferences[i] = enabled[i];
    }

    return SECSuccess;
}

PRCallOnceType gWeakDHParamsRegisterOnce;
int gWeakDHParamsRegisterError;

PRCallOnceType gWeakDHParamsOnce;
int gWeakDHParamsError;
/* As our code allocates type PQGParams, we'll keep it around,
 * even though we only make use of it's parameters through gWeakDHParam. */
static PQGParams *gWeakParamsPQG;
static ssl3DHParams *gWeakDHParams;
#define WEAK_DHE_SIZE 1024

static PRStatus
ssl3_CreateWeakDHParams(void)
{
    PQGVerify *vfy;
    SECStatus rv, passed;

    PORT_Assert(!gWeakDHParams && !gWeakParamsPQG);

    rv = PK11_PQG_ParamGenV2(WEAK_DHE_SIZE, 160, 64 /*maximum seed that will work*/,
                             &gWeakParamsPQG, &vfy);
    if (rv != SECSuccess) {
        gWeakDHParamsError = PORT_GetError();
        return PR_FAILURE;
    }

    rv = PK11_PQG_VerifyParams(gWeakParamsPQG, vfy, &passed);
    if (rv != SECSuccess || passed != SECSuccess) {
        SSL_DBG(("%d: PK11_PQG_VerifyParams failed in ssl3_CreateWeakDHParams",
                 SSL_GETPID()));
        gWeakDHParamsError = PORT_GetError();
        return PR_FAILURE;
    }

    gWeakDHParams = PORT_ArenaNew(gWeakParamsPQG->arena, ssl3DHParams);
    if (!gWeakDHParams) {
        gWeakDHParamsError = PORT_GetError();
        return PR_FAILURE;
    }

    gWeakDHParams->name = ssl_grp_ffdhe_custom;
    gWeakDHParams->prime.data = gWeakParamsPQG->prime.data;
    gWeakDHParams->prime.len = gWeakParamsPQG->prime.len;
    gWeakDHParams->base.data = gWeakParamsPQG->base.data;
    gWeakDHParams->base.len = gWeakParamsPQG->base.len;

    PK11_PQG_DestroyVerify(vfy);
    return PR_SUCCESS;
}

static SECStatus
ssl3_WeakDHParamsShutdown(void *appData, void *nssData)
{
    if (gWeakParamsPQG) {
        PK11_PQG_DestroyParams(gWeakParamsPQG);
        gWeakParamsPQG = NULL;
        gWeakDHParams = NULL;
    }
    return SECSuccess;
}

static PRStatus
ssl3_WeakDHParamsRegisterShutdown(void)
{
    SECStatus rv;
    rv = NSS_RegisterShutdown(ssl3_WeakDHParamsShutdown, NULL);
    if (rv != SECSuccess) {
        gWeakDHParamsRegisterError = PORT_GetError();
    }
    return (PRStatus)rv;
}

/* global init strategy inspired by ssl3_CreateECDHEphemeralKeys */
SECStatus
SSL_EnableWeakDHEPrimeGroup(PRFileDesc *fd, PRBool enabled)
{
    sslSocket *ss;
    PRStatus status;

    if (enabled) {
        status = PR_CallOnce(&gWeakDHParamsRegisterOnce,
                             ssl3_WeakDHParamsRegisterShutdown);
        if (status != PR_SUCCESS) {
            PORT_SetError(gWeakDHParamsRegisterError);
            return SECFailure;
        }

        status = PR_CallOnce(&gWeakDHParamsOnce, ssl3_CreateWeakDHParams);
        if (status != PR_SUCCESS) {
            PORT_SetError(gWeakDHParamsError);
            return SECFailure;
        }
    }

    if (!fd)
        return SECSuccess;

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_DHEGroupPrefSet", SSL_GETPID(), fd));
        return SECFailure;
    }

    ss->ssl3.dheWeakGroupEnabled = enabled;
    return SECSuccess;
}

#include "dhe-param.c"

const ssl3DHParams *
ssl_GetDHEParams(const sslNamedGroupDef *groupDef)
{
    switch (groupDef->name) {
        case ssl_grp_ffdhe_2048:
            return &ff_dhe_2048_params;
        case ssl_grp_ffdhe_3072:
            return &ff_dhe_3072_params;
        case ssl_grp_ffdhe_4096:
            return &ff_dhe_4096_params;
        case ssl_grp_ffdhe_6144:
            return &ff_dhe_6144_params;
        case ssl_grp_ffdhe_8192:
            return &ff_dhe_8192_params;
        case ssl_grp_ffdhe_custom:
            PORT_Assert(gWeakDHParams);
            return gWeakDHParams;
        default:
            PORT_Assert(0);
    }
    return NULL;
}

/* This validates dh_Ys against the group prime. */
PRBool
ssl_IsValidDHEShare(const SECItem *dh_p, const SECItem *dh_Ys)
{
    unsigned int size_p = SECKEY_BigIntegerBitLength(dh_p);
    unsigned int size_y = SECKEY_BigIntegerBitLength(dh_Ys);
    unsigned int commonPart;
    int cmp;

    if (dh_p->len == 0 || dh_Ys->len == 0) {
        return PR_FALSE;
    }
    /* Check that the prime is at least odd. */
    if ((dh_p->data[dh_p->len - 1] & 0x01) == 0) {
        return PR_FALSE;
    }
    /* dh_Ys can't be 1, or bigger than dh_p. */
    if (size_y <= 1 || size_y > size_p) {
        return PR_FALSE;
    }
    /* If dh_Ys is shorter, then it's definitely smaller than p-1. */
    if (size_y < size_p) {
        return PR_TRUE;
    }

    /* Compare the common part of each, minus the final octet. */
    commonPart = (size_p + 7) / 8;
    PORT_Assert(commonPart <= dh_Ys->len);
    PORT_Assert(commonPart <= dh_p->len);
    cmp = PORT_Memcmp(dh_Ys->data + dh_Ys->len - commonPart,
                      dh_p->data + dh_p->len - commonPart, commonPart - 1);
    if (cmp < 0) {
        return PR_TRUE;
    }
    if (cmp > 0) {
        return PR_FALSE;
    }

    /* The last octet of the prime is the only thing that is different and that
     * has to be two greater than the share, otherwise we have Ys == p - 1,
     * and that means small subgroups. */
    if (dh_Ys->data[dh_Ys->len - 1] >= (dh_p->data[dh_p->len - 1] - 1)) {
        return PR_FALSE;
    }

    return PR_TRUE;
}

/* Checks that the provided DH parameters match those in one of the named groups
 * that we have enabled.  The groups are defined in dhe-param.c and are those
 * defined in Appendix A of draft-ietf-tls-negotiated-ff-dhe.
 *
 * |groupDef| and |dhParams| are optional outparams that identify the group and
 * its parameters respectively (if this is successful). */
SECStatus
ssl_ValidateDHENamedGroup(sslSocket *ss,
                          const SECItem *dh_p,
                          const SECItem *dh_g,
                          const sslNamedGroupDef **groupDef,
                          const ssl3DHParams **dhParams)
{
    unsigned int i;

    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        const ssl3DHParams *params;
        if (!ss->namedGroupPreferences[i]) {
            continue;
        }
        if (ss->namedGroupPreferences[i]->keaType != ssl_kea_dh) {
            continue;
        }

        params = ssl_GetDHEParams(ss->namedGroupPreferences[i]);
        PORT_Assert(params);
        if (SECITEM_ItemsAreEqual(&params->prime, dh_p)) {
            if (!SECITEM_ItemsAreEqual(&params->base, dh_g)) {
                return SECFailure;
            }
            if (groupDef)
                *groupDef = ss->namedGroupPreferences[i];
            if (dhParams)
                *dhParams = params;
            return SECSuccess;
        }
    }

    return SECFailure;
}

/* Ensure DH parameters have been selected.  This just picks the first enabled
 * FFDHE group in ssl_named_groups, or the weak one if it was enabled. */
SECStatus
ssl_SelectDHEGroup(sslSocket *ss, const sslNamedGroupDef **groupDef)
{
    unsigned int i;
    static const sslNamedGroupDef weak_group_def = {
        ssl_grp_ffdhe_custom, WEAK_DHE_SIZE, ssl_kea_dh,
        SEC_OID_TLS_DHE_CUSTOM, PR_TRUE
    };

    /* Only select weak groups in TLS 1.2 and earlier, but not if the client has
     * indicated that it supports an FFDHE named group. */
    if (ss->ssl3.dheWeakGroupEnabled &&
        ss->version < SSL_LIBRARY_VERSION_TLS_1_3 &&
        !ss->xtnData.peerSupportsFfdheGroups) {
        *groupDef = &weak_group_def;
        return SECSuccess;
    }
    if (ss->ssl3.dhePreferredGroup &&
        ssl_NamedGroupEnabled(ss, ss->ssl3.dhePreferredGroup)) {
        *groupDef = ss->ssl3.dhePreferredGroup;
        return SECSuccess;
    }
    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        if (ss->namedGroupPreferences[i] &&
            ss->namedGroupPreferences[i]->keaType == ssl_kea_dh) {
            *groupDef = ss->namedGroupPreferences[i];
            return SECSuccess;
        }
    }

    *groupDef = NULL;
    PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
    return SECFailure;
}

/* LOCKS ??? XXX */
static PRFileDesc *
ssl_ImportFD(PRFileDesc *model, PRFileDesc *fd, SSLProtocolVariant variant)
{
    sslSocket *ns = NULL;
    PRStatus rv;
    PRNetAddr addr;
    SECStatus status = ssl_Init();

    if (status != SECSuccess) {
        return NULL;
    }

    if (model == NULL) {
        /* Just create a default socket if we're given NULL for the model */
        ns = ssl_NewSocket((PRBool)(!ssl_defaults.noLocks), variant);
    } else {
        sslSocket *ss = ssl_FindSocket(model);
        if (ss == NULL || ss->protocolVariant != variant) {
            SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ImportFD",
                     SSL_GETPID(), model));
            return NULL;
        }
        ns = ssl_DupSocket(ss);
    }
    if (ns == NULL)
        return NULL;

    rv = ssl_PushIOLayer(ns, fd, PR_TOP_IO_LAYER);
    if (rv != PR_SUCCESS) {
        ssl_FreeSocket(ns);
        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
        return NULL;
    }
#if defined(DEBUG) || defined(FORCE_PR_ASSERT)
    {
        sslSocket *ss = ssl_FindSocket(fd);
        PORT_Assert(ss == ns);
    }
#endif
    ns->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ns, &addr));
    return fd;
}

PRFileDesc *
SSL_ImportFD(PRFileDesc *model, PRFileDesc *fd)
{
    return ssl_ImportFD(model, fd, ssl_variant_stream);
}

PRFileDesc *
DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd)
{
    return ssl_ImportFD(model, fd, ssl_variant_datagram);
}

/* SSL_SetNextProtoCallback is used to select an application protocol
 * for ALPN. */
SECStatus
SSL_SetNextProtoCallback(PRFileDesc *fd, SSLNextProtoCallback callback,
                         void *arg)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoCallback", SSL_GETPID(),
                 fd));
        return SECFailure;
    }

    ssl_GetSSL3HandshakeLock(ss);
    ss->nextProtoCallback = callback;
    ss->nextProtoArg = arg;
    ssl_ReleaseSSL3HandshakeLock(ss);

    return SECSuccess;
}

/* ssl_NextProtoNegoCallback is set as an ALPN callback when
 * SSL_SetNextProtoNego is used.
 */
static SECStatus
ssl_NextProtoNegoCallback(void *arg, PRFileDesc *fd,
                          const unsigned char *protos, unsigned int protos_len,
                          unsigned char *protoOut, unsigned int *protoOutLen,
                          unsigned int protoMaxLen)
{
    unsigned int i, j;
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in ssl_NextProtoNegoCallback",
                 SSL_GETPID(), fd));
        return SECFailure;
    }
    PORT_Assert(protoMaxLen <= 255);
    if (protoMaxLen > 255) {
        PORT_SetError(SEC_ERROR_OUTPUT_LEN);
        return SECFailure;
    }

    /* For each protocol in client preference, see if we support it. */
    for (j = 0; j < ss->opt.nextProtoNego.len;) {
        for (i = 0; i < protos_len;) {
            if (protos[i] == ss->opt.nextProtoNego.data[j] &&
                PORT_Memcmp(&protos[i + 1], &ss->opt.nextProtoNego.data[j + 1],
                            protos[i]) == 0) {
                /* We found a match. */
                const unsigned char *result = &protos[i];
                memcpy(protoOut, result + 1, result[0]);
                *protoOutLen = result[0];
                return SECSuccess;
            }
            i += 1 + (unsigned int)protos[i];
        }
        j += 1 + (unsigned int)ss->opt.nextProtoNego.data[j];
    }

    return SECSuccess;
}

SECStatus
SSL_SetNextProtoNego(PRFileDesc *fd, const unsigned char *data,
                     unsigned int length)
{
    sslSocket *ss;

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetNextProtoNego",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    if (ssl3_ValidateAppProtocol(data, length) != SECSuccess) {
        return SECFailure;
    }

    /* NPN required that the client's fallback protocol is first in the
     * list. However, ALPN sends protocols in preference order. So move the
     * first protocol to the end of the list. */
    ssl_GetSSL3HandshakeLock(ss);
    SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
    SECITEM_AllocItem(NULL, &ss->opt.nextProtoNego, length);
    size_t firstLen = data[0] + 1;
    /* firstLen <= length is ensured by ssl3_ValidateAppProtocol. */
    PORT_Memcpy(ss->opt.nextProtoNego.data + (length - firstLen), data, firstLen);
    PORT_Memcpy(ss->opt.nextProtoNego.data, data + firstLen, length - firstLen);
    ssl_ReleaseSSL3HandshakeLock(ss);

    return SSL_SetNextProtoCallback(fd, ssl_NextProtoNegoCallback, NULL);
}

SECStatus
SSL_GetNextProto(PRFileDesc *fd, SSLNextProtoState *state, unsigned char *buf,
                 unsigned int *bufLen, unsigned int bufLenMax)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetNextProto", SSL_GETPID(),
                 fd));
        return SECFailure;
    }

    if (!state || !buf || !bufLen) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    *state = ss->xtnData.nextProtoState;

    if (ss->xtnData.nextProtoState != SSL_NEXT_PROTO_NO_SUPPORT &&
        ss->xtnData.nextProto.data) {
        if (ss->xtnData.nextProto.len > bufLenMax) {
            PORT_SetError(SEC_ERROR_OUTPUT_LEN);
            return SECFailure;
        }
        PORT_Memcpy(buf, ss->xtnData.nextProto.data, ss->xtnData.nextProto.len);
        *bufLen = ss->xtnData.nextProto.len;
    } else {
        *bufLen = 0;
    }

    return SECSuccess;
}

SECStatus
SSL_SetSRTPCiphers(PRFileDesc *fd,
                   const PRUint16 *ciphers,
                   unsigned int numCiphers)
{
    sslSocket *ss;
    unsigned int i;

    ss = ssl_FindSocket(fd);
    if (!ss || !IS_DTLS(ss)) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetSRTPCiphers",
                 SSL_GETPID(), fd));
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (numCiphers > MAX_DTLS_SRTP_CIPHER_SUITES) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ss->ssl3.dtlsSRTPCipherCount = 0;
    for (i = 0; i < numCiphers; i++) {
        const PRUint16 *srtpCipher = srtpCiphers;

        while (*srtpCipher) {
            if (ciphers[i] == *srtpCipher)
                break;
            srtpCipher++;
        }
        if (*srtpCipher) {
            ss->ssl3.dtlsSRTPCiphers[ss->ssl3.dtlsSRTPCipherCount++] =
                ciphers[i];
        } else {
            SSL_DBG(("%d: SSL[%d]: invalid or unimplemented SRTP cipher "
                     "suite specified: 0x%04hx",
                     SSL_GETPID(), fd,
                     ciphers[i]));
        }
    }

    if (ss->ssl3.dtlsSRTPCipherCount == 0) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    return SECSuccess;
}

SECStatus
SSL_GetSRTPCipher(PRFileDesc *fd, PRUint16 *cipher)
{
    sslSocket *ss;

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_GetSRTPCipher",
                 SSL_GETPID(), fd));
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!ss->xtnData.dtlsSRTPCipherSuite) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    *cipher = ss->xtnData.dtlsSRTPCipherSuite;
    return SECSuccess;
}

PRFileDesc *
SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
{
    sslSocket *sm = NULL, *ss = NULL;
    PRCList *cursor;

    if (model == NULL) {
        PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
        return NULL;
    }
    sm = ssl_FindSocket(model);
    if (sm == NULL) {
        SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ReconfigFD",
                 SSL_GETPID(), model));
        return NULL;
    }
    ss = ssl_FindSocket(fd);
    PORT_Assert(ss);
    if (ss == NULL) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    ss->opt = sm->opt;
    ss->vrange = sm->vrange;
    PORT_Memcpy(ss->cipherSuites, sm->cipherSuites, sizeof sm->cipherSuites);
    PORT_Memcpy(ss->ssl3.dtlsSRTPCiphers, sm->ssl3.dtlsSRTPCiphers,
                sizeof(PRUint16) * sm->ssl3.dtlsSRTPCipherCount);
    ss->ssl3.dtlsSRTPCipherCount = sm->ssl3.dtlsSRTPCipherCount;
    PORT_Memcpy(ss->ssl3.signatureSchemes, sm->ssl3.signatureSchemes,
                sizeof(ss->ssl3.signatureSchemes[0]) *
                    sm->ssl3.signatureSchemeCount);
    ss->ssl3.signatureSchemeCount = sm->ssl3.signatureSchemeCount;
    ss->ssl3.downgradeCheckVersion = sm->ssl3.downgradeCheckVersion;

    if (!ss->opt.useSecurity) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }
    while (!PR_CLIST_IS_EMPTY(&ss->serverCerts)) {
        cursor = PR_LIST_TAIL(&ss->serverCerts);
        PR_REMOVE_LINK(cursor);
        ssl_FreeServerCert((sslServerCert *)cursor);
    }
    for (cursor = PR_NEXT_LINK(&sm->serverCerts);
         cursor != &sm->serverCerts;
         cursor = PR_NEXT_LINK(cursor)) {
        sslServerCert *sc = ssl_CopyServerCert((sslServerCert *)cursor);
        if (!sc)
            return NULL;
        PR_APPEND_LINK(&sc->link, &ss->serverCerts);
    }

    ssl_FreeEphemeralKeyPairs(ss);
    for (cursor = PR_NEXT_LINK(&sm->ephemeralKeyPairs);
         cursor != &sm->ephemeralKeyPairs;
         cursor = PR_NEXT_LINK(cursor)) {
        sslEphemeralKeyPair *mkp = (sslEphemeralKeyPair *)cursor;
        sslEphemeralKeyPair *skp = ssl_CopyEphemeralKeyPair(mkp);
        if (!skp)
            return NULL;
        PR_APPEND_LINK(&skp->link, &ss->ephemeralKeyPairs);
    }

    while (!PR_CLIST_IS_EMPTY(&ss->extensionHooks)) {
        cursor = PR_LIST_TAIL(&ss->extensionHooks);
        PR_REMOVE_LINK(cursor);
        PORT_Free(cursor);
    }
    for (cursor = PR_NEXT_LINK(&sm->extensionHooks);
         cursor != &sm->extensionHooks;
         cursor = PR_NEXT_LINK(cursor)) {
        SECStatus rv;
        sslCustomExtensionHooks *hook = (sslCustomExtensionHooks *)cursor;
        rv = SSL_InstallExtensionHooks(ss->fd, hook->type,
                                       hook->writer, hook->writerArg,
                                       hook->handler, hook->handlerArg);
        if (rv != SECSuccess) {
            return NULL;
        }
    }

    PORT_Memcpy((void *)ss->namedGroupPreferences,
                sm->namedGroupPreferences,
                sizeof(ss->namedGroupPreferences));
    ss->additionalShares = sm->additionalShares;

    /* copy trust anchor names */
    if (sm->ssl3.ca_list) {
        if (ss->ssl3.ca_list) {
            CERT_FreeDistNames(ss->ssl3.ca_list);
        }
        ss->ssl3.ca_list = CERT_DupDistNames(sm->ssl3.ca_list);
        if (!ss->ssl3.ca_list) {
            return NULL;
        }
    }

    if (sm->authCertificate)
        ss->authCertificate = sm->authCertificate;
    if (sm->authCertificateArg)
        ss->authCertificateArg = sm->authCertificateArg;
    if (sm->getClientAuthData)
        ss->getClientAuthData = sm->getClientAuthData;
    if (sm->getClientAuthDataArg)
        ss->getClientAuthDataArg = sm->getClientAuthDataArg;
    if (sm->sniSocketConfig)
        ss->sniSocketConfig = sm->sniSocketConfig;
    if (sm->sniSocketConfigArg)
        ss->sniSocketConfigArg = sm->sniSocketConfigArg;
    if (sm->alertReceivedCallback) {
        ss->alertReceivedCallback = sm->alertReceivedCallback;
        ss->alertReceivedCallbackArg = sm->alertReceivedCallbackArg;
    }
    if (sm->alertSentCallback) {
        ss->alertSentCallback = sm->alertSentCallback;
        ss->alertSentCallbackArg = sm->alertSentCallbackArg;
    }
    if (sm->handleBadCert)
        ss->handleBadCert = sm->handleBadCert;
    if (sm->badCertArg)
        ss->badCertArg = sm->badCertArg;
    if (sm->handshakeCallback)
        ss->handshakeCallback = sm->handshakeCallback;
    if (sm->handshakeCallbackData)
        ss->handshakeCallbackData = sm->handshakeCallbackData;
    if (sm->pkcs11PinArg)
        ss->pkcs11PinArg = sm->pkcs11PinArg;
    return fd;
}

SECStatus
ssl3_GetEffectiveVersionPolicy(SSLProtocolVariant variant,
                               SSLVersionRange *effectivePolicy)
{
    SECStatus rv;
    PRUint32 policyFlag;
    PRInt32 minPolicy, maxPolicy;

    if (variant == ssl_variant_stream) {
        effectivePolicy->min = SSL_LIBRARY_VERSION_MIN_SUPPORTED_STREAM;
        effectivePolicy->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
    } else {
        effectivePolicy->min = SSL_LIBRARY_VERSION_MIN_SUPPORTED_DATAGRAM;
        effectivePolicy->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
    }

    rv = NSS_GetAlgorithmPolicy(SEC_OID_APPLY_SSL_POLICY, &policyFlag);
    if ((rv != SECSuccess) || !(policyFlag & NSS_USE_POLICY_IN_SSL)) {
        /* Policy is not active, report library extents. */
        return SECSuccess;
    }

    rv = NSS_OptionGet(VERSIONS_POLICY_MIN(variant), &minPolicy);
    if (rv != SECSuccess) {
        return SECFailure;
    }
    rv = NSS_OptionGet(VERSIONS_POLICY_MAX(variant), &maxPolicy);
    if (rv != SECSuccess) {
        return SECFailure;
    }

    if (minPolicy > effectivePolicy->max ||
        maxPolicy < effectivePolicy->min ||
        minPolicy > maxPolicy) {
        return SECFailure;
    }
    effectivePolicy->min = PR_MAX(effectivePolicy->min, minPolicy);
    effectivePolicy->max = PR_MIN(effectivePolicy->max, maxPolicy);
    return SECSuccess;
}

/*
 * Assumes that rangeParam values are within the supported boundaries,
 * but should contain all potentially allowed versions, even if they contain
 * conflicting versions.
 * Will return the overlap, or a NONE range if system policy is invalid.
 */
static SECStatus
ssl3_CreateOverlapWithPolicy(SSLProtocolVariant protocolVariant,
                             SSLVersionRange *input,
                             SSLVersionRange *overlap)
{
    SECStatus rv;
    SSLVersionRange effectivePolicyBoundary;
    SSLVersionRange vrange;

    PORT_Assert(input != NULL);

    rv = ssl3_GetEffectiveVersionPolicy(protocolVariant,
                                        &effectivePolicyBoundary);
    if (rv == SECFailure) {
        /* SECFailure means internal failure or invalid configuration. */
        overlap->min = overlap->max = SSL_LIBRARY_VERSION_NONE;
        return SECFailure;
    }

    vrange.min = PR_MAX(input->min, effectivePolicyBoundary.min);
    vrange.max = PR_MIN(input->max, effectivePolicyBoundary.max);

    if (vrange.max < vrange.min) {
        /* there was no overlap, turn off range altogether */
        overlap->min = overlap->max = SSL_LIBRARY_VERSION_NONE;
        return SECFailure;
    }

    *overlap = vrange;
    return SECSuccess;
}

static PRBool
ssl_VersionIsSupportedByPolicy(SSLProtocolVariant protocolVariant,
                               SSL3ProtocolVersion version)
{
    SECStatus rv;
    SSLVersionRange effectivePolicyBoundary;

    rv = ssl3_GetEffectiveVersionPolicy(protocolVariant,
                                        &effectivePolicyBoundary);
    if (rv == SECFailure) {
        /* SECFailure means internal failure or invalid configuration. */
        return PR_FALSE;
    }
    return version >= effectivePolicyBoundary.min &&
           version <= effectivePolicyBoundary.max;
}

/*
 *  This is called at SSL init time to constrain the existing range based
 *  on user supplied policy.
 */
SECStatus
ssl3_ConstrainRangeByPolicy(void)
{
    /* We ignore failures in ssl3_CreateOverlapWithPolicy. Although an empty
     * overlap disables all connectivity, it's an allowed state.
     */
    ssl3_CreateOverlapWithPolicy(ssl_variant_stream,
                                 VERSIONS_DEFAULTS(ssl_variant_stream),
                                 VERSIONS_DEFAULTS(ssl_variant_stream));
    ssl3_CreateOverlapWithPolicy(ssl_variant_datagram,
                                 VERSIONS_DEFAULTS(ssl_variant_datagram),
                                 VERSIONS_DEFAULTS(ssl_variant_datagram));
    return SECSuccess;
}

PRBool
ssl3_VersionIsSupportedByCode(SSLProtocolVariant protocolVariant,
                              SSL3ProtocolVersion version)
{
    switch (protocolVariant) {
        case ssl_variant_stream:
            return (version >= SSL_LIBRARY_VERSION_MIN_SUPPORTED_STREAM &&
                    version <= SSL_LIBRARY_VERSION_MAX_SUPPORTED);
        case ssl_variant_datagram:
            return (version >= SSL_LIBRARY_VERSION_MIN_SUPPORTED_DATAGRAM &&
                    version <= SSL_LIBRARY_VERSION_MAX_SUPPORTED);
    }

    /* Can't get here */
    PORT_Assert(PR_FALSE);
    return PR_FALSE;
}

PRBool
ssl3_VersionIsSupported(SSLProtocolVariant protocolVariant,
                        SSL3ProtocolVersion version)
{
    if (!ssl_VersionIsSupportedByPolicy(protocolVariant, version)) {
        return PR_FALSE;
    }
    return ssl3_VersionIsSupportedByCode(protocolVariant, version);
}

const SECItem *
SSL_PeerSignedCertTimestamps(PRFileDesc *fd)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_PeerSignedCertTimestamps",
                 SSL_GETPID(), fd));
        return NULL;
    }

    if (!ss->sec.ci.sid) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return NULL;
    }

    return &ss->sec.ci.sid->u.ssl3.signedCertTimestamps;
}

SECStatus
SSL_VersionRangeGetSupported(SSLProtocolVariant protocolVariant,
                             SSLVersionRange *vrange)
{
    SECStatus rv;

    if (!vrange) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    switch (protocolVariant) {
        case ssl_variant_stream:
            vrange->min = SSL_LIBRARY_VERSION_MIN_SUPPORTED_STREAM;
            vrange->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
            /* We don't allow SSLv3 and TLSv1.3 together.
             * However, don't check yet, apply the policy first.
             * Because if the effective supported range doesn't use TLS 1.3,
             * then we don't need to increase the minimum. */
            break;
        case ssl_variant_datagram:
            vrange->min = SSL_LIBRARY_VERSION_MIN_SUPPORTED_DATAGRAM;
            vrange->max = SSL_LIBRARY_VERSION_MAX_SUPPORTED;
            break;
        default:
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return SECFailure;
    }

    rv = ssl3_CreateOverlapWithPolicy(protocolVariant, vrange, vrange);
    if (rv != SECSuccess) {
        /* Library default and policy don't overlap. */
        return rv;
    }

    /* We don't allow SSLv3 and TLSv1.3 together */
    if (vrange->max >= SSL_LIBRARY_VERSION_TLS_1_3) {
        vrange->min = PR_MAX(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
    }

    return SECSuccess;
}

SECStatus
SSL_VersionRangeGetDefault(SSLProtocolVariant protocolVariant,
                           SSLVersionRange *vrange)
{
    if ((protocolVariant != ssl_variant_stream &&
         protocolVariant != ssl_variant_datagram) ||
        !vrange) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    *vrange = *VERSIONS_DEFAULTS(protocolVariant);
    return ssl3_CreateOverlapWithPolicy(protocolVariant, vrange, vrange);
}

static PRBool
ssl3_HasConflictingSSLVersions(const SSLVersionRange *vrange)
{
    return (vrange->min <= SSL_LIBRARY_VERSION_3_0 &&
            vrange->max >= SSL_LIBRARY_VERSION_TLS_1_3);
}

static SECStatus
ssl3_CheckRangeValidAndConstrainByPolicy(SSLProtocolVariant protocolVariant,
                                         SSLVersionRange *vrange)
{
    SECStatus rv;

    if (vrange->min > vrange->max ||
        !ssl3_VersionIsSupportedByCode(protocolVariant, vrange->min) ||
        !ssl3_VersionIsSupportedByCode(protocolVariant, vrange->max) ||
        ssl3_HasConflictingSSLVersions(vrange)) {
        PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
        return SECFailure;
    }

    /* Try to adjust the received range using our policy.
     * If there's overlap, we'll use the (possibly reduced) range.
     * If there isn't overlap, it's failure. */

    rv = ssl3_CreateOverlapWithPolicy(protocolVariant, vrange, vrange);
    if (rv != SECSuccess) {
        return rv;
    }

    /* We don't allow SSLv3 and TLSv1.3 together */
    if (vrange->max >= SSL_LIBRARY_VERSION_TLS_1_3) {
        vrange->min = PR_MAX(vrange->min, SSL_LIBRARY_VERSION_TLS_1_0);
    }

    return SECSuccess;
}

SECStatus
SSL_VersionRangeSetDefault(SSLProtocolVariant protocolVariant,
                           const SSLVersionRange *vrange)
{
    SSLVersionRange constrainedRange;
    SECStatus rv;

    if (!vrange) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    constrainedRange = *vrange;
    rv = ssl3_CheckRangeValidAndConstrainByPolicy(protocolVariant,
                                                  &constrainedRange);
    if (rv != SECSuccess)
        return rv;

    *VERSIONS_DEFAULTS(protocolVariant) = constrainedRange;
    return SECSuccess;
}

SECStatus
SSL_VersionRangeGet(PRFileDesc *fd, SSLVersionRange *vrange)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_VersionRangeGet",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    if (!vrange) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    *vrange = ss->vrange;

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return ssl3_CreateOverlapWithPolicy(ss->protocolVariant, vrange, vrange);
}

SECStatus
SSL_VersionRangeSet(PRFileDesc *fd, const SSLVersionRange *vrange)
{
    SSLVersionRange constrainedRange;
    sslSocket *ss;
    SECStatus rv;

    if (!vrange) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_VersionRangeSet",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    constrainedRange = *vrange;
    rv = ssl3_CheckRangeValidAndConstrainByPolicy(ss->protocolVariant,
                                                  &constrainedRange);
    if (rv != SECSuccess)
        return rv;

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    if (ss->ssl3.downgradeCheckVersion &&
        ss->vrange.max > ss->ssl3.downgradeCheckVersion) {
        PORT_SetError(SSL_ERROR_INVALID_VERSION_RANGE);
        ssl_ReleaseSSL3HandshakeLock(ss);
        ssl_Release1stHandshakeLock(ss);
        return SECFailure;
    }

    ss->vrange = constrainedRange;

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return SECSuccess;
}

SECStatus
SSL_SetDowngradeCheckVersion(PRFileDesc *fd, PRUint16 version)
{
    sslSocket *ss = ssl_FindSocket(fd);
    SECStatus rv = SECFailure;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetDowngradeCheckVersion",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    if (version && !ssl3_VersionIsSupported(ss->protocolVariant, version)) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    if (version && version < ss->vrange.max) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        goto loser;
    }
    ss->ssl3.downgradeCheckVersion = version;
    rv = SECSuccess;

loser:
    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return rv;
}

const SECItemArray *
SSL_PeerStapledOCSPResponses(PRFileDesc *fd)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_PeerStapledOCSPResponses",
                 SSL_GETPID(), fd));
        return NULL;
    }

    if (!ss->sec.ci.sid) {
        PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
        return NULL;
    }

    return &ss->sec.ci.sid->peerCertStatus;
}

/************************************************************************/
/* The following functions are the TOP LEVEL SSL functions.
** They all get called through the NSPRIOMethods table below.
*/

static PRFileDesc *PR_CALLBACK
ssl_Accept(PRFileDesc *fd, PRNetAddr *sockaddr, PRIntervalTime timeout)
{
    sslSocket *ss;
    sslSocket *ns = NULL;
    PRFileDesc *newfd = NULL;
    PRFileDesc *osfd;
    PRStatus status;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in accept", SSL_GETPID(), fd));
        return NULL;
    }

    /* IF this is a listen socket, there shouldn't be any I/O going on */
    SSL_LOCK_READER(ss);
    SSL_LOCK_WRITER(ss);
    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    ss->cTimeout = timeout;

    osfd = ss->fd->lower;

    /* First accept connection */
    newfd = osfd->methods->accept(osfd, sockaddr, timeout);
    if (newfd == NULL) {
        SSL_DBG(("%d: SSL[%d]: accept failed, errno=%d",
                 SSL_GETPID(), ss->fd, PORT_GetError()));
    } else {
        /* Create ssl module */
        ns = ssl_DupSocket(ss);
    }

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);
    SSL_UNLOCK_WRITER(ss);
    SSL_UNLOCK_READER(ss); /* ss isn't used below here. */

    if (ns == NULL)
        goto loser;

    /* push ssl module onto the new socket */
    status = ssl_PushIOLayer(ns, newfd, PR_TOP_IO_LAYER);
    if (status != PR_SUCCESS)
        goto loser;

    /* Now start server connection handshake with client.
    ** Don't need locks here because nobody else has a reference to ns yet.
    */
    if (ns->opt.useSecurity) {
        if (ns->opt.handshakeAsClient) {
            ns->handshake = ssl_BeginClientHandshake;
            ss->handshaking = sslHandshakingAsClient;
        } else {
            ns->handshake = ssl_BeginServerHandshake;
            ss->handshaking = sslHandshakingAsServer;
        }
    }
    ns->TCPconnected = 1;
    return newfd;

loser:
    if (ns != NULL)
        ssl_FreeSocket(ns);
    if (newfd != NULL)
        PR_Close(newfd);
    return NULL;
}

static PRStatus PR_CALLBACK
ssl_Connect(PRFileDesc *fd, const PRNetAddr *sockaddr, PRIntervalTime timeout)
{
    sslSocket *ss;
    PRStatus rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in connect", SSL_GETPID(), fd));
        return PR_FAILURE;
    }

    /* IF this is a listen socket, there shouldn't be any I/O going on */
    SSL_LOCK_READER(ss);
    SSL_LOCK_WRITER(ss);

    ss->cTimeout = timeout;
    rv = (PRStatus)(*ss->ops->connect)(ss, sockaddr);

    SSL_UNLOCK_WRITER(ss);
    SSL_UNLOCK_READER(ss);

    return rv;
}

static PRStatus PR_CALLBACK
ssl_Bind(PRFileDesc *fd, const PRNetAddr *addr)
{
    sslSocket *ss = ssl_GetPrivate(fd);
    PRStatus rv;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in bind", SSL_GETPID(), fd));
        return PR_FAILURE;
    }
    SSL_LOCK_READER(ss);
    SSL_LOCK_WRITER(ss);

    rv = (PRStatus)(*ss->ops->bind)(ss, addr);

    SSL_UNLOCK_WRITER(ss);
    SSL_UNLOCK_READER(ss);
    return rv;
}

static PRStatus PR_CALLBACK
ssl_Listen(PRFileDesc *fd, PRIntn backlog)
{
    sslSocket *ss = ssl_GetPrivate(fd);
    PRStatus rv;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in listen", SSL_GETPID(), fd));
        return PR_FAILURE;
    }
    SSL_LOCK_READER(ss);
    SSL_LOCK_WRITER(ss);

    rv = (PRStatus)(*ss->ops->listen)(ss, backlog);

    SSL_UNLOCK_WRITER(ss);
    SSL_UNLOCK_READER(ss);
    return rv;
}

static PRStatus PR_CALLBACK
ssl_Shutdown(PRFileDesc *fd, PRIntn how)
{
    sslSocket *ss = ssl_GetPrivate(fd);
    PRStatus rv;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in shutdown", SSL_GETPID(), fd));
        return PR_FAILURE;
    }
    if (how == PR_SHUTDOWN_RCV || how == PR_SHUTDOWN_BOTH) {
        SSL_LOCK_READER(ss);
    }
    if (how == PR_SHUTDOWN_SEND || how == PR_SHUTDOWN_BOTH) {
        SSL_LOCK_WRITER(ss);
    }

    rv = (PRStatus)(*ss->ops->shutdown)(ss, how);

    if (how == PR_SHUTDOWN_SEND || how == PR_SHUTDOWN_BOTH) {
        SSL_UNLOCK_WRITER(ss);
    }
    if (how == PR_SHUTDOWN_RCV || how == PR_SHUTDOWN_BOTH) {
        SSL_UNLOCK_READER(ss);
    }
    return rv;
}

static PRStatus PR_CALLBACK
ssl_Close(PRFileDesc *fd)
{
    sslSocket *ss;
    PRStatus rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in close", SSL_GETPID(), fd));
        return PR_FAILURE;
    }

    /* There must not be any I/O going on */
    SSL_LOCK_READER(ss);
    SSL_LOCK_WRITER(ss);

    /* By the time this function returns,
    ** ss is an invalid pointer, and the locks to which it points have
    ** been unlocked and freed.  So, this is the ONE PLACE in all of SSL
    ** where the LOCK calls and the corresponding UNLOCK calls are not in
    ** the same function scope.  The unlock calls are in ssl_FreeSocket().
    */
    rv = (PRStatus)(*ss->ops->close)(ss);

    return rv;
}

static int PR_CALLBACK
ssl_Recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags,
         PRIntervalTime timeout)
{
    sslSocket *ss;
    int rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in recv", SSL_GETPID(), fd));
        return SECFailure;
    }
    SSL_LOCK_READER(ss);
    ss->rTimeout = timeout;
    if (!ss->opt.fdx)
        ss->wTimeout = timeout;
    rv = (*ss->ops->recv)(ss, (unsigned char *)buf, len, flags);
    SSL_UNLOCK_READER(ss);
    return rv;
}

static int PR_CALLBACK
ssl_Send(PRFileDesc *fd, const void *buf, PRInt32 len, PRIntn flags,
         PRIntervalTime timeout)
{
    sslSocket *ss;
    int rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in send", SSL_GETPID(), fd));
        return SECFailure;
    }
    SSL_LOCK_WRITER(ss);
    ss->wTimeout = timeout;
    if (!ss->opt.fdx)
        ss->rTimeout = timeout;
    rv = (*ss->ops->send)(ss, (const unsigned char *)buf, len, flags);
    SSL_UNLOCK_WRITER(ss);
    return rv;
}

static int PR_CALLBACK
ssl_Read(PRFileDesc *fd, void *buf, PRInt32 len)
{
    sslSocket *ss;
    int rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in read", SSL_GETPID(), fd));
        return SECFailure;
    }
    SSL_LOCK_READER(ss);
    ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
    if (!ss->opt.fdx)
        ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
    rv = (*ss->ops->read)(ss, (unsigned char *)buf, len);
    SSL_UNLOCK_READER(ss);
    return rv;
}

static int PR_CALLBACK
ssl_Write(PRFileDesc *fd, const void *buf, PRInt32 len)
{
    sslSocket *ss;
    int rv;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in write", SSL_GETPID(), fd));
        return SECFailure;
    }
    SSL_LOCK_WRITER(ss);
    ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
    if (!ss->opt.fdx)
        ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
    rv = (*ss->ops->write)(ss, (const unsigned char *)buf, len);
    SSL_UNLOCK_WRITER(ss);
    return rv;
}

static PRStatus PR_CALLBACK
ssl_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
{
    sslSocket *ss;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in getpeername", SSL_GETPID(), fd));
        return PR_FAILURE;
    }
    return (PRStatus)(*ss->ops->getpeername)(ss, addr);
}

/*
*/
SECStatus
ssl_GetPeerInfo(sslSocket *ss)
{
    PRFileDesc *osfd;
    int rv;
    PRNetAddr sin;

    osfd = ss->fd->lower;

    PORT_Memset(&sin, 0, sizeof(sin));
    rv = osfd->methods->getpeername(osfd, &sin);
    if (rv < 0) {
        return SECFailure;
    }
    ss->TCPconnected = 1;
    if (sin.inet.family == PR_AF_INET) {
        PR_ConvertIPv4AddrToIPv6(sin.inet.ip, &ss->sec.ci.peer);
        ss->sec.ci.port = sin.inet.port;
    } else if (sin.ipv6.family == PR_AF_INET6) {
        ss->sec.ci.peer = sin.ipv6.ip;
        ss->sec.ci.port = sin.ipv6.port;
    } else {
        PORT_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR);
        return SECFailure;
    }
    return SECSuccess;
}

static PRStatus PR_CALLBACK
ssl_GetSockName(PRFileDesc *fd, PRNetAddr *name)
{
    sslSocket *ss;

    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in getsockname", SSL_GETPID(), fd));
        return PR_FAILURE;
    }
    return (PRStatus)(*ss->ops->getsockname)(ss, name);
}

SECStatus
SSL_SetSockPeerID(PRFileDesc *fd, const char *peerID)
{
    sslSocket *ss;

    ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetSockPeerID",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    if (ss->peerID) {
        PORT_Free(ss->peerID);
        ss->peerID = NULL;
    }
    if (peerID)
        ss->peerID = PORT_Strdup(peerID);
    return (ss->peerID || !peerID) ? SECSuccess : SECFailure;
}

#define PR_POLL_RW (PR_POLL_WRITE | PR_POLL_READ)

static PRInt16 PR_CALLBACK
ssl_Poll(PRFileDesc *fd, PRInt16 how_flags, PRInt16 *p_out_flags)
{
    sslSocket *ss;
    PRInt16 new_flags = how_flags; /* should select on these flags. */
    PRNetAddr addr;

    *p_out_flags = 0;
    ss = ssl_GetPrivate(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_Poll",
                 SSL_GETPID(), fd));
        return 0; /* don't poll on this socket */
    }

    if (ss->opt.useSecurity &&
        ss->handshaking != sslHandshakingUndetermined &&
        !ss->firstHsDone &&
        (how_flags & PR_POLL_RW)) {
        if (!ss->TCPconnected) {
            ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr));
        }
        /* If it's not connected, then presumably the application is polling
        ** on read or write appropriately, so don't change it.
        */
        if (ss->TCPconnected) {
            if (!ss->handshakeBegun) {
                /* If the handshake has not begun, poll on read or write
                ** based on the local application's role in the handshake,
                ** not based on what the application requested.
                */
                new_flags &= ~PR_POLL_RW;
                if (ss->handshaking == sslHandshakingAsClient) {
                    new_flags |= PR_POLL_WRITE;
                } else { /* handshaking as server */
                    new_flags |= PR_POLL_READ;
                }
            } else if (ss->lastWriteBlocked) {
                /* First handshake is in progress */
                if (new_flags & PR_POLL_READ) {
                    /* The caller is waiting for data to be received,
                    ** but the initial handshake is blocked on write, or the
                    ** client's first handshake record has not been written.
                    ** The code should select on write, not read.
                    */
                    new_flags &= ~PR_POLL_READ; /* don't select on read. */
                    new_flags |= PR_POLL_WRITE; /* do    select on write. */
                }
            } else if (new_flags & PR_POLL_WRITE) {
                /* The caller is trying to write, but the handshake is
                ** blocked waiting for data to read, and the first
                ** handshake has been sent.  So do NOT to poll on write
                ** unless we did false start or we are doing 0-RTT.
                */
                if (!(ss->ssl3.hs.canFalseStart ||
                      ss->ssl3.hs.zeroRttState == ssl_0rtt_sent ||
                      ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted)) {
                    new_flags &= ~PR_POLL_WRITE; /* don't select on write. */
                }
                new_flags |= PR_POLL_READ; /* do    select on read. */
            }
        }
    } else if ((new_flags & PR_POLL_READ) && (SSL_DataPending(fd) > 0)) {
        *p_out_flags = PR_POLL_READ; /* it's ready already. */
        return new_flags;
    } else if ((ss->lastWriteBlocked) && (how_flags & PR_POLL_READ) &&
               (ss->pendingBuf.len != 0)) { /* write data waiting to be sent */
        new_flags |= PR_POLL_WRITE;         /* also select on write. */
    }

    if (ss->ssl3.hs.restartTarget != NULL) {
        /* Read and write will block until the asynchronous callback completes
         * (e.g. until SSL_AuthCertificateComplete is called), so don't tell
         * the caller to poll the socket unless there is pending write data.
         */
        if (ss->lastWriteBlocked && ss->pendingBuf.len != 0) {
            /* Ignore any newly-received data on the socket, but do wait for
             * the socket to become writable again. Here, it is OK for an error
             * to be detected, because our logic for sending pending write data
             * will allow us to report the error to the caller without the risk
             * of the application spinning.
             */
            new_flags &= (PR_POLL_WRITE | PR_POLL_EXCEPT);
        } else {
            /* Unfortunately, clearing new_flags will make it impossible for
             * the application to detect errors that it would otherwise be
             * able to detect with PR_POLL_EXCEPT, until the asynchronous
             * callback completes. However, we must clear all the flags to
             * prevent the application from spinning (alternating between
             * calling PR_Poll that would return PR_POLL_EXCEPT, and send/recv
             * which won't actually report the I/O error while we are waiting
             * for the asynchronous callback to complete).
             */
            new_flags = 0;
        }
    }

    SSL_TRC(20, ("%d: SSL[%d]: ssl_Poll flags %x -> %x",
                 SSL_GETPID(), fd, how_flags, new_flags));

    if (new_flags && (fd->lower->methods->poll != NULL)) {
        PRInt16 lower_out_flags = 0;
        PRInt16 lower_new_flags;
        lower_new_flags = fd->lower->methods->poll(fd->lower, new_flags,
                                                   &lower_out_flags);
        if ((lower_new_flags & lower_out_flags) && (how_flags != new_flags)) {
            PRInt16 out_flags = lower_out_flags & ~PR_POLL_RW;
            if (lower_out_flags & PR_POLL_READ)
                out_flags |= PR_POLL_WRITE;
            if (lower_out_flags & PR_POLL_WRITE)
                out_flags |= PR_POLL_READ;
            *p_out_flags = out_flags;
            new_flags = how_flags;
        } else {
            *p_out_flags = lower_out_flags;
            new_flags = lower_new_flags;
        }
    }

    return new_flags;
}

static PRInt32 PR_CALLBACK
ssl_TransmitFile(PRFileDesc *sd, PRFileDesc *fd,
                 const void *headers, PRInt32 hlen,
                 PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    PRSendFileData sfd;

    sfd.fd = fd;
    sfd.file_offset = 0;
    sfd.file_nbytes = 0;
    sfd.header = headers;
    sfd.hlen = hlen;
    sfd.trailer = NULL;
    sfd.tlen = 0;

    return sd->methods->sendfile(sd, &sfd, flags, timeout);
}

PRBool
ssl_FdIsBlocking(PRFileDesc *fd)
{
    PRSocketOptionData opt;
    PRStatus status;

    opt.option = PR_SockOpt_Nonblocking;
    opt.value.non_blocking = PR_FALSE;
    status = PR_GetSocketOption(fd, &opt);
    if (status != PR_SUCCESS)
        return PR_FALSE;
    return (PRBool)!opt.value.non_blocking;
}

PRBool
ssl_SocketIsBlocking(sslSocket *ss)
{
    return ssl_FdIsBlocking(ss->fd);
}

PRInt32 sslFirstBufSize = 8 * 1024;
PRInt32 sslCopyLimit = 1024;

static PRInt32 PR_CALLBACK
ssl_WriteV(PRFileDesc *fd, const PRIOVec *iov, PRInt32 vectors,
           PRIntervalTime timeout)
{
    PRInt32 i;
    PRInt32 bufLen;
    PRInt32 left;
    PRInt32 rv;
    PRInt32 sent = 0;
    const PRInt32 first_len = sslFirstBufSize;
    const PRInt32 limit = sslCopyLimit;
    PRBool blocking;
    PRIOVec myIov;
    char buf[MAX_FRAGMENT_LENGTH];

    if (vectors < 0) {
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
        return -1;
    }
    if (vectors > PR_MAX_IOVECTOR_SIZE) {
        PORT_SetError(PR_BUFFER_OVERFLOW_ERROR);
        return -1;
    }
    for (i = 0; i < vectors; i++) {
        if (iov[i].iov_len < 0) {
            PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
            return -1;
        }
    }
    blocking = ssl_FdIsBlocking(fd);

#define K16 ((int)sizeof(buf))
#define KILL_VECTORS                   \
    while (vectors && !iov->iov_len) { \
        ++iov;                         \
        --vectors;                     \
    }
#define GET_VECTOR      \
    do {                \
        myIov = *iov++; \
        --vectors;      \
        KILL_VECTORS    \
    } while (0)
#define HANDLE_ERR(rv, len)                                    \
    if (rv != len) {                                           \
        if (rv < 0) {                                          \
            if (!blocking &&                                   \
                (PR_GetError() == PR_WOULD_BLOCK_ERROR) &&     \
                (sent > 0)) {                                  \
                return sent;                                   \
            } else {                                           \
                return -1;                                     \
            }                                                  \
        }                                                      \
        /* Only a nonblocking socket can have partial sends */ \
        PR_ASSERT(!blocking);                                  \
        return sent + rv;                                      \
    }
#define SEND(bfr, len)                           \
    do {                                         \
        rv = ssl_Send(fd, bfr, len, 0, timeout); \
        HANDLE_ERR(rv, len)                      \
        sent += len;                             \
    } while (0)

    /* Make sure the first write is at least 8 KB, if possible. */
    KILL_VECTORS
    if (!vectors)
        return ssl_Send(fd, 0, 0, 0, timeout);
    GET_VECTOR;
    if (!vectors) {
        return ssl_Send(fd, myIov.iov_base, myIov.iov_len, 0, timeout);
    }
    if (myIov.iov_len < first_len) {
        PORT_Memcpy(buf, myIov.iov_base, myIov.iov_len);
        bufLen = myIov.iov_len;
        left = first_len - bufLen;
        while (vectors && left) {
            int toCopy;
            GET_VECTOR;
            toCopy = PR_MIN(left, myIov.iov_len);
            PORT_Memcpy(buf + bufLen, myIov.iov_base, toCopy);
            bufLen += toCopy;
            left -= toCopy;
            myIov.iov_base += toCopy;
            myIov.iov_len -= toCopy;
        }
        SEND(buf, bufLen);
    }

    while (vectors || myIov.iov_len) {
        PRInt32 addLen;
        if (!myIov.iov_len) {
            GET_VECTOR;
        }
        while (myIov.iov_len >= K16) {
            SEND(myIov.iov_base, K16);
            myIov.iov_base += K16;
            myIov.iov_len -= K16;
        }
        if (!myIov.iov_len)
            continue;

        if (!vectors || myIov.iov_len > limit) {
            addLen = 0;
        } else if ((addLen = iov->iov_len % K16) + myIov.iov_len <= limit) {
            /* Addlen is already computed. */;
        } else if (vectors > 1 &&
                   iov[1].iov_len % K16 + addLen + myIov.iov_len <= 2 * limit) {
            addLen = limit - myIov.iov_len;
        } else
            addLen = 0;

        if (!addLen) {
            SEND(myIov.iov_base, myIov.iov_len);
            myIov.iov_len = 0;
            continue;
        }
        PORT_Memcpy(buf, myIov.iov_base, myIov.iov_len);
        bufLen = myIov.iov_len;
        do {
            GET_VECTOR;
            PORT_Memcpy(buf + bufLen, myIov.iov_base, addLen);
            myIov.iov_base += addLen;
            myIov.iov_len -= addLen;
            bufLen += addLen;

            left = PR_MIN(limit, K16 - bufLen);
            if (!vectors             /* no more left */
                || myIov.iov_len > 0 /* we didn't use that one all up */
                || bufLen >= K16 /* it's full. */) {
                addLen = 0;
            } else if ((addLen = iov->iov_len % K16) <= left) {
                /* Addlen is already computed. */;
            } else if (vectors > 1 &&
                       iov[1].iov_len % K16 + addLen <= left + limit) {
                addLen = left;
            } else
                addLen = 0;

        } while (addLen);
        SEND(buf, bufLen);
    }
    return sent;
}

/*
 * These functions aren't implemented.
 */

static PRInt32 PR_CALLBACK
ssl_Available(PRFileDesc *fd)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return SECFailure;
}

static PRInt64 PR_CALLBACK
ssl_Available64(PRFileDesc *fd)
{
    PRInt64 res;

    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    LL_I2L(res, -1L);
    return res;
}

static PRStatus PR_CALLBACK
ssl_FSync(PRFileDesc *fd)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

static PRInt32 PR_CALLBACK
ssl_Seek(PRFileDesc *fd, PRInt32 offset, PRSeekWhence how)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return SECFailure;
}

static PRInt64 PR_CALLBACK
ssl_Seek64(PRFileDesc *fd, PRInt64 offset, PRSeekWhence how)
{
    PRInt64 res;

    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    LL_I2L(res, -1L);
    return res;
}

static PRStatus PR_CALLBACK
ssl_FileInfo(PRFileDesc *fd, PRFileInfo *info)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

static PRStatus PR_CALLBACK
ssl_FileInfo64(PRFileDesc *fd, PRFileInfo64 *info)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

static PRInt32 PR_CALLBACK
ssl_RecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
             PRNetAddr *addr, PRIntervalTime timeout)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return SECFailure;
}

static PRInt32 PR_CALLBACK
ssl_SendTo(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
           const PRNetAddr *addr, PRIntervalTime timeout)
{
    PORT_Assert(0);
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return SECFailure;
}

static const PRIOMethods ssl_methods = {
    PR_DESC_LAYERED,
    ssl_Close,            /* close        */
    ssl_Read,             /* read         */
    ssl_Write,            /* write        */
    ssl_Available,        /* available    */
    ssl_Available64,      /* available64  */
    ssl_FSync,            /* fsync        */
    ssl_Seek,             /* seek         */
    ssl_Seek64,           /* seek64       */
    ssl_FileInfo,         /* fileInfo     */
    ssl_FileInfo64,       /* fileInfo64   */
    ssl_WriteV,           /* writev       */
    ssl_Connect,          /* connect      */
    ssl_Accept,           /* accept       */
    ssl_Bind,             /* bind         */
    ssl_Listen,           /* listen       */
    ssl_Shutdown,         /* shutdown     */
    ssl_Recv,             /* recv         */
    ssl_Send,             /* send         */
    ssl_RecvFrom,         /* recvfrom     */
    ssl_SendTo,           /* sendto       */
    ssl_Poll,             /* poll         */
    PR_EmulateAcceptRead, /* acceptread   */
    ssl_TransmitFile,     /* transmitfile */
    ssl_GetSockName,      /* getsockname  */
    ssl_GetPeerName,      /* getpeername  */
    NULL,                 /* getsockopt   OBSOLETE */
    NULL,                 /* setsockopt   OBSOLETE */
    NULL,                 /* getsocketoption   */
    NULL,                 /* setsocketoption   */
    PR_EmulateSendFile,   /* Send a (partial) file with header/trailer*/
    NULL,                 /* reserved for future use */
    NULL,                 /* reserved for future use */
    NULL,                 /* reserved for future use */
    NULL,                 /* reserved for future use */
    NULL                  /* reserved for future use */
};

static PRIOMethods combined_methods;

static void
ssl_SetupIOMethods(void)
{
    PRIOMethods *new_methods = &combined_methods;
    const PRIOMethods *nspr_methods = PR_GetDefaultIOMethods();
    const PRIOMethods *my_methods = &ssl_methods;

    *new_methods = *nspr_methods;

    new_methods->file_type = my_methods->file_type;
    new_methods->close = my_methods->close;
    new_methods->read = my_methods->read;
    new_methods->write = my_methods->write;
    new_methods->available = my_methods->available;
    new_methods->available64 = my_methods->available64;
    new_methods->fsync = my_methods->fsync;
    new_methods->seek = my_methods->seek;
    new_methods->seek64 = my_methods->seek64;
    new_methods->fileInfo = my_methods->fileInfo;
    new_methods->fileInfo64 = my_methods->fileInfo64;
    new_methods->writev = my_methods->writev;
    new_methods->connect = my_methods->connect;
    new_methods->accept = my_methods->accept;
    new_methods->bind = my_methods->bind;
    new_methods->listen = my_methods->listen;
    new_methods->shutdown = my_methods->shutdown;
    new_methods->recv = my_methods->recv;
    new_methods->send = my_methods->send;
    new_methods->recvfrom = my_methods->recvfrom;
    new_methods->sendto = my_methods->sendto;
    new_methods->poll = my_methods->poll;
    new_methods->acceptread = my_methods->acceptread;
    new_methods->transmitfile = my_methods->transmitfile;
    new_methods->getsockname = my_methods->getsockname;
    new_methods->getpeername = my_methods->getpeername;
    /*  new_methods->getsocketoption   = my_methods->getsocketoption;       */
    /*  new_methods->setsocketoption   = my_methods->setsocketoption;       */
    new_methods->sendfile = my_methods->sendfile;
}

static PRCallOnceType initIoLayerOnce;

static PRStatus
ssl_InitIOLayer(void)
{
    ssl_layer_id = PR_GetUniqueIdentity("SSL");
    ssl_SetupIOMethods();
    return PR_SUCCESS;
}

static PRStatus
ssl_PushIOLayer(sslSocket *ns, PRFileDesc *stack, PRDescIdentity id)
{
    PRFileDesc *layer = NULL;
    PRStatus status;

    status = PR_CallOnce(&initIoLayerOnce, &ssl_InitIOLayer);
    if (status != PR_SUCCESS) {
        goto loser;
    }
    if (ns == NULL) {
        goto loser;
    }
    layer = PR_CreateIOLayerStub(ssl_layer_id, &combined_methods);
    if (layer == NULL)
        goto loser;
    layer->secret = (PRFilePrivate *)ns;

    /* Here, "stack" points to the PRFileDesc on the top of the stack.
    ** "layer" points to a new FD that is to be inserted into the stack.
    ** If layer is being pushed onto the top of the stack, then
    ** PR_PushIOLayer switches the contents of stack and layer, and then
    ** puts stack on top of layer, so that after it is done, the top of
    ** stack is the same "stack" as it was before, and layer is now the
    ** FD for the former top of stack.
    ** After this call, stack always points to the top PRFD on the stack.
    ** If this function fails, the contents of stack and layer are as
    ** they were before the call.
    */
    status = PR_PushIOLayer(stack, id, layer);
    if (status != PR_SUCCESS)
        goto loser;

    ns->fd = (id == PR_TOP_IO_LAYER) ? stack : layer;
    return PR_SUCCESS;

loser:
    if (layer) {
        layer->dtor(layer); /* free layer */
    }
    return PR_FAILURE;
}

/* if this fails, caller must destroy socket. */
static SECStatus
ssl_MakeLocks(sslSocket *ss)
{
    ss->firstHandshakeLock = PZ_NewMonitor(nssILockSSL);
    if (!ss->firstHandshakeLock)
        goto loser;
    ss->ssl3HandshakeLock = PZ_NewMonitor(nssILockSSL);
    if (!ss->ssl3HandshakeLock)
        goto loser;
    ss->specLock = NSSRWLock_New(SSL_LOCK_RANK_SPEC, NULL);
    if (!ss->specLock)
        goto loser;
    ss->recvBufLock = PZ_NewMonitor(nssILockSSL);
    if (!ss->recvBufLock)
        goto loser;
    ss->xmitBufLock = PZ_NewMonitor(nssILockSSL);
    if (!ss->xmitBufLock)
        goto loser;
    ss->writerThread = NULL;
    if (ssl_lock_readers) {
        ss->recvLock = PZ_NewLock(nssILockSSL);
        if (!ss->recvLock)
            goto loser;
        ss->sendLock = PZ_NewLock(nssILockSSL);
        if (!ss->sendLock)
            goto loser;
    }
    return SECSuccess;
loser:
    ssl_DestroyLocks(ss);
    return SECFailure;
}

#if defined(XP_UNIX) || defined(XP_WIN32) || defined(XP_BEOS)
#define NSS_HAVE_GETENV 1
#endif

#define LOWER(x) (x | 0x20) /* cheap ToLower function ignores LOCALE */

static void
ssl_SetDefaultsFromEnvironment(void)
{
#if defined(NSS_HAVE_GETENV)
    static int firsttime = 1;

    if (firsttime) {
        char *ev;
        firsttime = 0;
#ifdef DEBUG
        ssl_trace_iob = NULL;
        ev = PR_GetEnvSecure("SSLDEBUGFILE");
        if (ev && ev[0]) {
            ssl_trace_iob = fopen(ev, "w");
        }
        if (!ssl_trace_iob) {
            ssl_trace_iob = stderr;
        }
#ifdef TRACE
        ev = PR_GetEnvSecure("SSLTRACE");
        if (ev && ev[0]) {
            ssl_trace = atoi(ev);
            SSL_TRACE(("SSL: tracing set to %d", ssl_trace));
        }
#endif /* TRACE */
        ev = PR_GetEnvSecure("SSLDEBUG");
        if (ev && ev[0]) {
            ssl_debug = atoi(ev);
            SSL_TRACE(("SSL: debugging set to %d", ssl_debug));
        }
#endif /* DEBUG */
#ifdef NSS_ALLOW_SSLKEYLOGFILE
        ssl_keylog_iob = NULL;
        ev = PR_GetEnvSecure("SSLKEYLOGFILE");
        if (ev && ev[0]) {
            ssl_keylog_iob = fopen(ev, "a");
            if (!ssl_keylog_iob) {
                SSL_TRACE(("SSL: failed to open key log file"));
            } else {
                if (ftell(ssl_keylog_iob) == 0) {
                    fputs("# SSL/TLS secrets log file, generated by NSS\n",
                          ssl_keylog_iob);
                }
                SSL_TRACE(("SSL: logging SSL/TLS secrets to %s", ev));
                ssl_keylog_lock = PR_NewLock();
                if (!ssl_keylog_lock) {
                    SSL_TRACE(("SSL: failed to create key log lock"));
                    fclose(ssl_keylog_iob);
                    ssl_keylog_iob = NULL;
                }
            }
        }
#endif
        ev = PR_GetEnvSecure("SSLFORCELOCKS");
        if (ev && ev[0] == '1') {
            ssl_force_locks = PR_TRUE;
            ssl_defaults.noLocks = 0;
            strcpy(lockStatus + LOCKSTATUS_OFFSET, "FORCED.  ");
            SSL_TRACE(("SSL: force_locks set to %d", ssl_force_locks));
        }
        ev = PR_GetEnvSecure("NSS_SSL_ENABLE_RENEGOTIATION");
        if (ev) {
            if (ev[0] == '1' || LOWER(ev[0]) == 'u')
                ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_UNRESTRICTED;
            else if (ev[0] == '0' || LOWER(ev[0]) == 'n')
                ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
            else if (ev[0] == '2' || LOWER(ev[0]) == 'r')
                ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_REQUIRES_XTN;
            else if (ev[0] == '3' || LOWER(ev[0]) == 't')
                ssl_defaults.enableRenegotiation = SSL_RENEGOTIATE_TRANSITIONAL;
            SSL_TRACE(("SSL: enableRenegotiation set to %d",
                       ssl_defaults.enableRenegotiation));
        }
        ev = PR_GetEnvSecure("NSS_SSL_REQUIRE_SAFE_NEGOTIATION");
        if (ev && ev[0] == '1') {
            ssl_defaults.requireSafeNegotiation = PR_TRUE;
            SSL_TRACE(("SSL: requireSafeNegotiation set to %d",
                       PR_TRUE));
        }
        ev = PR_GetEnvSecure("NSS_SSL_CBC_RANDOM_IV");
        if (ev && ev[0] == '0') {
            ssl_defaults.cbcRandomIV = PR_FALSE;
            SSL_TRACE(("SSL: cbcRandomIV set to 0"));
        }
    }
#endif /* NSS_HAVE_GETENV */
}

const sslNamedGroupDef *
ssl_LookupNamedGroup(SSLNamedGroup group)
{
    unsigned int i;

    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        if (ssl_named_groups[i].name == group) {
            return &ssl_named_groups[i];
        }
    }
    return NULL;
}

PRBool
ssl_NamedGroupEnabled(const sslSocket *ss, const sslNamedGroupDef *groupDef)
{
    unsigned int i;

    if (!groupDef) {
        return PR_FALSE;
    }

    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        if (ss->namedGroupPreferences[i] &&
            ss->namedGroupPreferences[i] == groupDef) {
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}

/* Returns a reference counted object that contains a key pair.
 * Or NULL on failure.  Initial ref count is 1.
 * Uses the keys in the pair as input.  Adopts the keys given.
 */
sslKeyPair *
ssl_NewKeyPair(SECKEYPrivateKey *privKey, SECKEYPublicKey *pubKey)
{
    sslKeyPair *pair;

    if (!privKey || !pubKey) {
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
        return NULL;
    }
    pair = PORT_ZNew(sslKeyPair);
    if (!pair)
        return NULL; /* error code is set. */
    pair->privKey = privKey;
    pair->pubKey = pubKey;
    pair->refCount = 1;
    return pair; /* success */
}

sslKeyPair *
ssl_GetKeyPairRef(sslKeyPair *keyPair)
{
    PR_ATOMIC_INCREMENT(&keyPair->refCount);
    return keyPair;
}

void
ssl_FreeKeyPair(sslKeyPair *keyPair)
{
    if (!keyPair) {
        return;
    }

    PRInt32 newCount = PR_ATOMIC_DECREMENT(&keyPair->refCount);
    if (!newCount) {
        SECKEY_DestroyPrivateKey(keyPair->privKey);
        SECKEY_DestroyPublicKey(keyPair->pubKey);
        PORT_Free(keyPair);
    }
}

/* Ephemeral key handling. */
sslEphemeralKeyPair *
ssl_NewEphemeralKeyPair(const sslNamedGroupDef *group,
                        SECKEYPrivateKey *privKey, SECKEYPublicKey *pubKey)
{
    sslKeyPair *keys;
    sslEphemeralKeyPair *pair;

    if (!group) {
        PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
        return NULL;
    }

    keys = ssl_NewKeyPair(privKey, pubKey);
    if (!keys) {
        return NULL;
    }

    pair = PORT_ZNew(sslEphemeralKeyPair);
    if (!pair) {
        ssl_FreeKeyPair(keys);
        return NULL; /* error already set */
    }

    PR_INIT_CLIST(&pair->link);
    pair->group = group;
    pair->keys = keys;

    return pair;
}

sslEphemeralKeyPair *
ssl_CopyEphemeralKeyPair(sslEphemeralKeyPair *keyPair)
{
    sslEphemeralKeyPair *pair;

    pair = PORT_ZNew(sslEphemeralKeyPair);
    if (!pair) {
        return NULL; /* error already set */
    }

    PR_INIT_CLIST(&pair->link);
    pair->group = keyPair->group;
    pair->keys = ssl_GetKeyPairRef(keyPair->keys);

    return pair;
}

void
ssl_FreeEphemeralKeyPair(sslEphemeralKeyPair *keyPair)
{
    if (!keyPair) {
        return;
    }

    ssl_FreeKeyPair(keyPair->keys);
    PR_REMOVE_LINK(&keyPair->link);
    PORT_Free(keyPair);
}

PRBool
ssl_HaveEphemeralKeyPair(const sslSocket *ss, const sslNamedGroupDef *groupDef)
{
    return ssl_LookupEphemeralKeyPair((sslSocket *)ss, groupDef) != NULL;
}

sslEphemeralKeyPair *
ssl_LookupEphemeralKeyPair(sslSocket *ss, const sslNamedGroupDef *groupDef)
{
    PRCList *cursor;
    for (cursor = PR_NEXT_LINK(&ss->ephemeralKeyPairs);
         cursor != &ss->ephemeralKeyPairs;
         cursor = PR_NEXT_LINK(cursor)) {
        sslEphemeralKeyPair *keyPair = (sslEphemeralKeyPair *)cursor;
        if (keyPair->group == groupDef) {
            return keyPair;
        }
    }
    return NULL;
}

void
ssl_FreeEphemeralKeyPairs(sslSocket *ss)
{
    while (!PR_CLIST_IS_EMPTY(&ss->ephemeralKeyPairs)) {
        PRCList *cursor = PR_LIST_TAIL(&ss->ephemeralKeyPairs);
        ssl_FreeEphemeralKeyPair((sslEphemeralKeyPair *)cursor);
    }
}

/*
** Create a newsocket structure for a file descriptor.
*/
static sslSocket *
ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
{
    SECStatus rv;
    sslSocket *ss;
    int i;
    ssl_SetDefaultsFromEnvironment();

    if (ssl_force_locks)
        makeLocks = PR_TRUE;

    /* Make a new socket and get it ready */
    ss = (sslSocket *)PORT_ZAlloc(sizeof(sslSocket));
    if (!ss) {
        return NULL;
    }
    ss->opt = ssl_defaults;
    if (protocolVariant == ssl_variant_datagram) {
        ss->opt.enableRenegotiation = SSL_RENEGOTIATE_NEVER;
    }
    ss->opt.useSocks = PR_FALSE;
    ss->opt.noLocks = !makeLocks;
    ss->vrange = *VERSIONS_DEFAULTS(protocolVariant);
    ss->protocolVariant = protocolVariant;
    /* Ignore overlap failures, because returning NULL would trigger assertion
     * failures elsewhere. We don't want this scenario to be fatal, it's just
     * a state where no SSL connectivity is possible. */
    ssl3_CreateOverlapWithPolicy(ss->protocolVariant, &ss->vrange, &ss->vrange);
    ss->peerID = NULL;
    ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
    ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
    ss->cTimeout = PR_INTERVAL_NO_TIMEOUT;
    ss->url = NULL;

    PR_INIT_CLIST(&ss->serverCerts);
    PR_INIT_CLIST(&ss->ephemeralKeyPairs);
    PR_INIT_CLIST(&ss->extensionHooks);

    ss->dbHandle = CERT_GetDefaultCertDB();

    /* Provide default implementation of hooks */
    ss->authCertificate = SSL_AuthCertificate;
    ss->authCertificateArg = (void *)ss->dbHandle;
    ss->sniSocketConfig = NULL;
    ss->sniSocketConfigArg = NULL;
    ss->getClientAuthData = NULL;
    ss->alertReceivedCallback = NULL;
    ss->alertReceivedCallbackArg = NULL;
    ss->alertSentCallback = NULL;
    ss->alertSentCallbackArg = NULL;
    ss->handleBadCert = NULL;
    ss->badCertArg = NULL;
    ss->pkcs11PinArg = NULL;

    ssl_ChooseOps(ss);
    ssl3_InitSocketPolicy(ss);
    for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
        ss->namedGroupPreferences[i] = &ssl_named_groups[i];
    }
    ss->additionalShares = 0;
    PR_INIT_CLIST(&ss->ssl3.hs.remoteExtensions);
    PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
    PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
    PR_INIT_CLIST(&ss->ssl3.hs.bufferedEarlyData);
    ssl3_InitExtensionData(&ss->xtnData, ss);
    PR_INIT_CLIST(&ss->ssl3.hs.dtlsSentHandshake);
    PR_INIT_CLIST(&ss->ssl3.hs.dtlsRcvdHandshake);
    dtls_InitTimers(ss);

    ss->esniKeys = NULL;

    if (makeLocks) {
        rv = ssl_MakeLocks(ss);
        if (rv != SECSuccess)
            goto loser;
    }
    rv = ssl_CreateSecurityInfo(ss);
    if (rv != SECSuccess)
        goto loser;
    rv = ssl3_InitGather(&ss->gs);
    if (rv != SECSuccess)
        goto loser;
    rv = ssl3_InitState(ss);
    if (rv != SECSuccess) {
        goto loser;
    }
    return ss;

loser:
    ssl_DestroySocketContents(ss);
    ssl_DestroyLocks(ss);
    PORT_Free(ss);
    return NULL;
}

/**
 * DEPRECATED: Will always return false.
 */
SECStatus
SSL_CanBypass(CERTCertificate *cert, SECKEYPrivateKey *srvPrivkey,
              PRUint32 protocolmask, PRUint16 *ciphersuites, int nsuites,
              PRBool *pcanbypass, void *pwArg)
{
    if (!pcanbypass) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    *pcanbypass = PR_FALSE;
    return SECSuccess;
}

/* Functions that are truly experimental use EXP, functions that are no longer
 * experimental use PUB.
 *
 * When initially defining a new API, add that API here using the EXP() macro
 * and name the function with a SSLExp_ prefix.  Define the experimental API as
 * a macro in sslexp.h using the SSL_EXPERIMENTAL_API() macro defined there.
 *
 * Once an API is stable and proven, move the macro definition in sslexp.h to a
 * proper function declaration in ssl.h.  Keeping the function in this list
 * ensures that code built against the release that contained the experimental
 * API will continue to work; use PUB() to reference the public function.
 */
#define EXP(n)                \
    {                         \
        "SSL_" #n, SSLExp_##n \
    }
#define PUB(n)             \
    {                      \
        "SSL_" #n, SSL_##n \
    }
struct {
    const char *const name;
    void *function;
} ssl_experimental_functions[] = {
#ifndef SSL_DISABLE_EXPERIMENTAL_API
    EXP(AeadDecrypt),
    EXP(AeadEncrypt),
    EXP(DestroyAead),
    EXP(DestroyResumptionTokenInfo),
    EXP(EnableESNI),
    EXP(EncodeESNIKeys),
    EXP(GetCurrentEpoch),
    EXP(GetExtensionSupport),
    EXP(GetResumptionTokenInfo),
    EXP(HelloRetryRequestCallback),
    EXP(InstallExtensionHooks),
    EXP(HkdfExtract),
    EXP(HkdfExpandLabel),
    EXP(HkdfExpandLabelWithMech),
    EXP(KeyUpdate),
    EXP(MakeAead),
    EXP(RecordLayerData),
    EXP(RecordLayerWriteCallback),
    EXP(SecretCallback),
    EXP(SendCertificateRequest),
    EXP(SendSessionTicket),
    EXP(SetESNIKeyPair),
    EXP(SetMaxEarlyDataSize),
    EXP(SetResumptionTokenCallback),
    EXP(SetResumptionToken),
    EXP(SetupAntiReplay),
#endif
    { "", NULL }
};
#undef EXP
#undef PUB

void *
SSL_GetExperimentalAPI(const char *name)
{
    unsigned int i;
    for (i = 0; i < PR_ARRAY_SIZE(ssl_experimental_functions); ++i) {
        if (strcmp(name, ssl_experimental_functions[i].name) == 0) {
            return ssl_experimental_functions[i].function;
        }
    }
    PORT_SetError(SSL_ERROR_UNSUPPORTED_EXPERIMENTAL_API);
    return NULL;
}

void
ssl_ClearPRCList(PRCList *list, void (*f)(void *))
{
    PRCList *cursor;

    while (!PR_CLIST_IS_EMPTY(list)) {
        cursor = PR_LIST_TAIL(list);

        PR_REMOVE_LINK(cursor);
        if (f) {
            f(cursor);
        }
        PORT_Free(cursor);
    }
}

/* Experimental APIs for session cache handling. */

SECStatus
SSLExp_SetResumptionTokenCallback(PRFileDesc *fd,
                                  SSLResumptionTokenCallback cb,
                                  void *ctx)
{
    sslSocket *ss = ssl_FindSocket(fd);

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetResumptionTokenCallback",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);
    ss->resumptionTokenCallback = cb;
    ss->resumptionTokenContext = ctx;
    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return SECSuccess;
}

SECStatus
SSLExp_SetResumptionToken(PRFileDesc *fd, const PRUint8 *token,
                          unsigned int len)
{
    sslSocket *ss = ssl_FindSocket(fd);
    sslSessionID *sid = NULL;

    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetResumptionToken",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    if (ss->firstHsDone || ss->ssl3.hs.ws != idle_handshake ||
        ss->sec.isServer || len == 0 || !token) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        goto loser;
    }

    // We override any previously set session.
    if (ss->sec.ci.sid) {
        ssl_FreeSID(ss->sec.ci.sid);
        ss->sec.ci.sid = NULL;
    }

    PRINT_BUF(50, (ss, "incoming resumption token", token, len));

    sid = ssl3_NewSessionID(ss, PR_FALSE);
    if (!sid) {
        goto loser;
    }

    /* Populate NewSessionTicket values */
    SECStatus rv = ssl_DecodeResumptionToken(sid, token, len);
    if (rv != SECSuccess) {
        // If decoding fails, we assume the token is bad.
        PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR);
        goto loser;
    }

    // Make sure that the token is currently usable.
    if (!ssl_IsResumptionTokenUsable(ss, sid)) {
        PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR);
        goto loser;
    }

    // Generate a new random session ID for this ticket.
    rv = PK11_GenerateRandom(sid->u.ssl3.sessionID, SSL3_SESSIONID_BYTES);
    if (rv != SECSuccess) {
        goto loser; // Code set by PK11_GenerateRandom.
    }
    sid->u.ssl3.sessionIDLength = SSL3_SESSIONID_BYTES;
    /* Use the sid->cached as marker that this is from an external cache and
     * we don't have to look up anything in the NSS internal cache. */
    sid->cached = in_external_cache;
    sid->lastAccessTime = ssl_TimeSec();

    ss->sec.ci.sid = sid;

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);
    return SECSuccess;

loser:
    ssl_FreeSID(sid);
    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return SECFailure;
}

SECStatus
SSLExp_DestroyResumptionTokenInfo(SSLResumptionTokenInfo *token)
{
    if (!token) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (token->peerCert) {
        CERT_DestroyCertificate(token->peerCert);
    }
    PORT_Free(token->alpnSelection);
    PORT_Memset(token, 0, token->length);
    return SECSuccess;
}

SECStatus
SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int tokenLen,
                              SSLResumptionTokenInfo *tokenOut, PRUintn len)
{
    if (!tokenData || !tokenOut || !tokenLen ||
        len > sizeof(SSLResumptionTokenInfo)) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    sslSessionID sid = { 0 };
    SSLResumptionTokenInfo token;

    /* Populate sid values */
    if (ssl_DecodeResumptionToken(&sid, tokenData, tokenLen) != SECSuccess) {
        // If decoding fails, we assume the token is bad.
        PORT_SetError(SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR);
        return SECFailure;
    }

    token.peerCert = CERT_DupCertificate(sid.peerCert);

    token.alpnSelectionLen = sid.u.ssl3.alpnSelection.len;
    token.alpnSelection = PORT_ZAlloc(token.alpnSelectionLen);
    if (!token.alpnSelection) {
        return SECFailure;
    }
    PORT_Memcpy(token.alpnSelection, sid.u.ssl3.alpnSelection.data,
                token.alpnSelectionLen);

    if (sid.u.ssl3.locked.sessionTicket.flags & ticket_allow_early_data) {
        token.maxEarlyDataSize =
            sid.u.ssl3.locked.sessionTicket.max_early_data_size;
    } else {
        token.maxEarlyDataSize = 0;
    }
    token.expirationTime = sid.expirationTime;

    token.length = PR_MIN(sizeof(SSLResumptionTokenInfo), len);
    PORT_Memcpy(tokenOut, &token, token.length);

    ssl_DestroySID(&sid, PR_FALSE);
    return SECSuccess;
}