Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/security/nss/lib/softoken/legacydb/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 140 kB image not shown  

Quelle  pcertdb.c

  Sprache: C
 

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


/*
 * Permanent Certificate database handling code
 */

#include "lowkeyti.h"
#include "pcert.h"
#include "mcom_db.h"
#include "pcert.h"
#include "secitem.h"
#include "secder.h"

#include "secerr.h"
#include "lgdb.h"

/* forward declaration */
NSSLOWCERTCertificate *
nsslowcert_FindCertByDERCertNoLocking(NSSLOWCERTCertDBHandle *handle, SECItem *derCert);
static SECStatus
nsslowcert_UpdateSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle,
                              char *emailAddr, SECItem *derSubject, SECItem *emailProfile,
                              SECItem *profileTime);
static SECStatus
nsslowcert_UpdatePermCert(NSSLOWCERTCertDBHandle *dbhandle,
                          NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust);
static SECStatus
nsslowcert_UpdateCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl,
                     SECItem *crlKey, char *url, PRBool isKRL);

static NSSLOWCERTCertificate *certListHead = NULL;
static NSSLOWCERTTrust *trustListHead = NULL;
static certDBEntryCert *entryListHead = NULL;
static int certListCount = 0;
static int trustListCount = 0;
static int entryListCount = 0;
#define MAX_CERT_LIST_COUNT 10
#define MAX_TRUST_LIST_COUNT 10
#define MAX_ENTRY_LIST_COUNT 10

/*
 * the following functions are wrappers for the db library that implement
 * a global lock to make the database thread safe.
 */

static PZLock *dbLock = NULL;
static PZLock *certRefCountLock = NULL;
static PZLock *certTrustLock = NULL;
static PZLock *freeListLock = NULL;

void
certdb_InitDBLock(NSSLOWCERTCertDBHandle *handle)
{
    if (dbLock == NULL) {
        dbLock = PZ_NewLock(nssILockCertDB);
        PORT_Assert(dbLock != NULL);
    }
}

SECStatus
nsslowcert_InitLocks(void)
{
    if (freeListLock == NULL) {
        freeListLock = PZ_NewLock(nssILockRefLock);
        if (freeListLock == NULL) {
            return SECFailure;
        }
    }
    if (certRefCountLock == NULL) {
        certRefCountLock = PZ_NewLock(nssILockRefLock);
        if (certRefCountLock == NULL) {
            return SECFailure;
        }
    }
    if (certTrustLock == NULL) {
        certTrustLock = PZ_NewLock(nssILockCertDB);
        if (certTrustLock == NULL) {
            return SECFailure;
        }
    }

    return SECSuccess;
}

/*
 * Acquire the global lock on the cert database.
 * This lock is currently used for the following operations:
 *  adding or deleting a cert to either the temp or perm databases
 *  converting a temp to perm or perm to temp
 *  changing (maybe just adding!?) the trust of a cert
 *      chaning the DB status checking Configuration
 */

static void
nsslowcert_LockDB(NSSLOWCERTCertDBHandle *handle)
{
    PZ_EnterMonitor(handle->dbMon);
    return;
}

/*
 * Free the global cert database lock.
 */

static void
nsslowcert_UnlockDB(NSSLOWCERTCertDBHandle *handle)
{
#ifdef DEBUG
    PRStatus prstat = PZ_ExitMonitor(handle->dbMon);
    PORT_Assert(prstat == PR_SUCCESS);
#else
    PZ_ExitMonitor(handle->dbMon);
#endif
}

/*
 * Acquire the cert reference count lock
 * There is currently one global lock for all certs, but I'm putting a cert
 * arg here so that it will be easy to make it per-cert in the future if
 * that turns out to be necessary.
 */

static void
nsslowcert_LockCertRefCount(NSSLOWCERTCertificate *cert)
{
    PORT_Assert(certRefCountLock != NULL);

    PZ_Lock(certRefCountLock);
    return;
}

/*
 * Free the cert reference count lock
 */

static void
nsslowcert_UnlockCertRefCount(NSSLOWCERTCertificate *cert)
{
    PORT_Assert(certRefCountLock != NULL);

#ifdef DEBUG
    {
        PRStatus prstat = PZ_Unlock(certRefCountLock);
        PORT_Assert(prstat == PR_SUCCESS);
    }
#else
    PZ_Unlock(certRefCountLock);
#endif
}

/*
 * Acquire the cert trust lock
 * There is currently one global lock for all certs, but I'm putting a cert
 * arg here so that it will be easy to make it per-cert in the future if
 * that turns out to be necessary.
 */

static void
nsslowcert_LockCertTrust(NSSLOWCERTCertificate *cert)
{
    PORT_Assert(certTrustLock != NULL);

    PZ_Lock(certTrustLock);
    return;
}

/*
 * Free the cert trust lock
 */

static void
nsslowcert_UnlockCertTrust(NSSLOWCERTCertificate *cert)
{
    PORT_Assert(certTrustLock != NULL);

#ifdef DEBUG
    {
        PRStatus prstat = PZ_Unlock(certTrustLock);
        PORT_Assert(prstat == PR_SUCCESS);
    }
#else
    PZ_Unlock(certTrustLock);
#endif
}

/*
 * Acquire the cert reference count lock
 * There is currently one global lock for all certs, but I'm putting a cert
 * arg here so that it will be easy to make it per-cert in the future if
 * that turns out to be necessary.
 */

static void
nsslowcert_LockFreeList(void)
{
    PORT_Assert(freeListLock != NULL);

    SKIP_AFTER_FORK(PZ_Lock(freeListLock));
    return;
}

/*
 * Free the cert reference count lock
 */

static void
nsslowcert_UnlockFreeList(void)
{
    PORT_Assert(freeListLock != NULL);

#ifdef DEBUG
    {
        PRStatus prstat = PR_SUCCESS;
        SKIP_AFTER_FORK(prstat = PZ_Unlock(freeListLock));
        PORT_Assert(prstat == PR_SUCCESS);
    }
#else
    SKIP_AFTER_FORK(PZ_Unlock(freeListLock));
#endif
}

NSSLOWCERTCertificate *
nsslowcert_DupCertificate(NSSLOWCERTCertificate *c)
{
    if (c) {
        nsslowcert_LockCertRefCount(c);
        ++c->referenceCount;
        nsslowcert_UnlockCertRefCount(c);
    }
    return c;
}

static int
certdb_Get(DB *db, DBT *key, DBT *data, unsigned int flags)
{
    int ret;

    PORT_Assert(dbLock != NULL);
    PZ_Lock(dbLock);

    ret = (*db->get)(db, key, data, flags);

    (void)PZ_Unlock(dbLock);

    return (ret);
}

static int
certdb_Put(DB *db, DBT *key, DBT *data, unsigned int flags)
{
    int ret = 0;

    PORT_Assert(dbLock != NULL);
    PZ_Lock(dbLock);

    ret = (*db->put)(db, key, data, flags);

    (void)PZ_Unlock(dbLock);

    return (ret);
}

static int
certdb_Sync(DB *db, unsigned int flags)
{
    int ret;

    PORT_Assert(dbLock != NULL);
    PZ_Lock(dbLock);

    ret = (*db->sync)(db, flags);

    (void)PZ_Unlock(dbLock);

    return (ret);
}

#define DB_NOT_FOUND -30991 /* from DBM 3.2 */
static int
certdb_Del(DB *db, DBT *key, unsigned int flags)
{
    int ret;

    PORT_Assert(dbLock != NULL);
    PZ_Lock(dbLock);

    ret = (*db->del)(db, key, flags);

    (void)PZ_Unlock(dbLock);

    /* don't fail if the record is already deleted */
    if (ret == DB_NOT_FOUND) {
        ret = 0;
    }

    return (ret);
}

static int
certdb_Seq(DB *db, DBT *key, DBT *data, unsigned int flags)
{
    int ret;

    PORT_Assert(dbLock != NULL);
    PZ_Lock(dbLock);

    ret = (*db->seq)(db, key, data, flags);

    (void)PZ_Unlock(dbLock);

    return (ret);
}

static void
certdb_Close(DB *db)
{
    PORT_Assert(dbLock != NULL);
    SKIP_AFTER_FORK(PZ_Lock(dbLock));

    (*db->close)(db);

    SKIP_AFTER_FORK(PZ_Unlock(dbLock));

    return;
}

void
pkcs11_freeNickname(char *nickname, char *space)
{
    if (nickname && nickname != space) {
        PORT_Free(nickname);
    }
}

char *
pkcs11_copyNickname(char *nickname, char *space, int spaceLen)
{
    int len;
    char *copy = NULL;

    len = PORT_Strlen(nickname) + 1;
    if (len <= spaceLen) {
        copy = space;
        PORT_Memcpy(copy, nickname, len);
    } else {
        copy = PORT_Strdup(nickname);
    }

    return copy;
}

void
pkcs11_freeStaticData(unsigned char *data, unsigned char *space)
{
    if (data && data != space) {
        PORT_Free(data);
    }
}

unsigned char *
pkcs11_allocStaticData(int len, unsigned char *space, int spaceLen)
{
    unsigned char *data = NULL;

    if (len <= spaceLen) {
        data = space;
    } else {
        data = (unsigned char *)PORT_Alloc(len);
    }

    return data;
}

unsigned char *
pkcs11_copyStaticData(unsigned char *data, int len,
                      unsigned char *space, int spaceLen)
{
    unsigned char *copy = pkcs11_allocStaticData(len, space, spaceLen);
    if (copy) {
        PORT_Memcpy(copy, data, len);
    }

    return copy;
}

/*
 * destroy a database entry
 */

static void
DestroyDBEntry(certDBEntry *entry)
{
    PLArenaPool *arena = entry->common.arena;

    /* must be one of our certDBEntry from the free list */
    if (arena == NULL) {
        certDBEntryCert *certEntry;
        if (entry->common.type != certDBEntryTypeCert) {
            return;
        }
        certEntry = (certDBEntryCert *)entry;

        pkcs11_freeStaticData(certEntry->derCert.data, certEntry->derCertSpace);
        pkcs11_freeNickname(certEntry->nickname, certEntry->nicknameSpace);

        nsslowcert_LockFreeList();
        if (entryListCount > MAX_ENTRY_LIST_COUNT) {
            PORT_Free(certEntry);
        } else {
            entryListCount++;
            PORT_Memset(certEntry, 0, sizeof(*certEntry));
            certEntry->next = entryListHead;
            entryListHead = certEntry;
        }
        nsslowcert_UnlockFreeList();
        return;
    }

    /* Zero out the entry struct, so that any further attempts to use it
     * will cause an exception (e.g. null pointer reference). */

    PORT_Memset(&entry->common, 0, sizeof entry->common);
    PORT_FreeArena(arena, PR_FALSE);

    return;
}

/* forward references */
static void nsslowcert_DestroyCertificateNoLocking(NSSLOWCERTCertificate *cert);

static SECStatus
DeleteDBEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryType type, SECItem *dbkey)
{
    DBT key;
    int ret;

    /* init the database key */
    key.data = dbkey->data;
    key.size = dbkey->len;

    dbkey->data[0] = (unsigned char)type;

    /* delete entry from database */
    ret = certdb_Del(handle->permCertDB, &key, 0);
    if (ret != 0) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    ret = certdb_Sync(handle->permCertDB, 0);
    if (ret) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

static SECStatus
ReadDBEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryCommon *entry,
            SECItem *dbkey, SECItem *dbentry, PLArenaPool *arena)
{
    DBT data, key;
    int ret;
    unsigned char *buf;

    /* init the database key */
    key.data = dbkey->data;
    key.size = dbkey->len;

    dbkey->data[0] = (unsigned char)entry->type;

    /* read entry from database */
    ret = certdb_Get(handle->permCertDB, &key, &data, 0);
    if (ret != 0) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* validate the entry */
    if (data.size < SEC_DB_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }
    buf = (unsigned char *)data.data;
    /* version 7 has the same schema, we may be using a v7 db if we openned
     * the databases readonly. */

    if (!((buf[0] == (unsigned char)CERT_DB_FILE_VERSION) ||
          (buf[0] == (unsigned char)CERT_DB_V7_FILE_VERSION))) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }
    if (buf[1] != (unsigned char)entry->type) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* copy out header information */
    entry->version = (unsigned int)buf[0];
    entry->type = (certDBEntryType)buf[1];
    entry->flags = (unsigned int)buf[2];

    /* format body of entry for return to caller */
    dbentry->len = data.size - SEC_DB_ENTRY_HEADER_LEN;
    if (dbentry->len) {
        if (arena) {
            dbentry->data = (unsigned char *)
                PORT_ArenaAlloc(arena, dbentry->len);
            if (dbentry->data == NULL) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }

            PORT_Memcpy(dbentry->data, &buf[SEC_DB_ENTRY_HEADER_LEN],
                        dbentry->len);
        } else {
            dbentry->data = &buf[SEC_DB_ENTRY_HEADER_LEN];
        }
    } else {
        dbentry->data = NULL;
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

/**
 ** Implement low level database access
 **/

static SECStatus
WriteDBEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryCommon *entry,
             SECItem *dbkey, SECItem *dbentry)
{
    int ret;
    DBT data, key;
    unsigned char *buf;

    data.data = dbentry->data;
    data.size = dbentry->len;

    buf = (unsigned char *)data.data;

    buf[0] = (unsigned char)entry->version;
    buf[1] = (unsigned char)entry->type;
    buf[2] = (unsigned char)entry->flags;

    key.data = dbkey->data;
    key.size = dbkey->len;

    dbkey->data[0] = (unsigned char)entry->type;

    /* put the record into the database now */
    ret = certdb_Put(handle->permCertDB, &key, &data, 0);

    if (ret != 0) {
        goto loser;
    }

    ret = certdb_Sync(handle->permCertDB, 0);

    if (ret) {
        goto loser;
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * encode a database cert record
 */

static SECStatus
EncodeDBCertEntry(certDBEntryCert *entry, PLArenaPool *arena, SECItem *dbitem)
{
    unsigned int nnlen;
    unsigned char *buf;
    char *nn;
    char zbuf = 0;

    if (entry->nickname) {
        nn = entry->nickname;
    } else {
        nn = &zbuf;
    }
    nnlen = PORT_Strlen(nn) + 1;

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem->len = entry->derCert.len + nnlen + DB_CERT_ENTRY_HEADER_LEN +
                  SEC_DB_ENTRY_HEADER_LEN;

    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
    if (dbitem->data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* fill in database record */
    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];

    buf[0] = (PRUint8)(entry->trust.sslFlags >> 8);
    buf[1] = (PRUint8)(entry->trust.sslFlags);
    buf[2] = (PRUint8)(entry->trust.emailFlags >> 8);
    buf[3] = (PRUint8)(entry->trust.emailFlags);
    buf[4] = (PRUint8)(entry->trust.objectSigningFlags >> 8);
    buf[5] = (PRUint8)(entry->trust.objectSigningFlags);
    buf[6] = (PRUint8)(entry->derCert.len >> 8);
    buf[7] = (PRUint8)(entry->derCert.len);
    buf[8] = (PRUint8)(nnlen >> 8);
    buf[9] = (PRUint8)(nnlen);

    PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN], entry->derCert.data,
                entry->derCert.len);

    PORT_Memcpy(&buf[DB_CERT_ENTRY_HEADER_LEN + entry->derCert.len],
                nn, nnlen);

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * encode a database key for a cert record
 */

static SECStatus
EncodeDBCertKey(const SECItem *certKey, PLArenaPool *arena, SECItem *dbkey)
{
    unsigned int len = certKey->len + SEC_DB_KEY_HEADER_LEN;
    if (len > NSS_MAX_LEGACY_DB_KEY_SIZE)
        goto loser;
    if (arena) {
        dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, len);
    } else {
        if (dbkey->len < len) {
            dbkey->data = (unsigned char *)PORT_Alloc(len);
        }
    }
    dbkey->len = len;
    if (dbkey->data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN],
                certKey->data, certKey->len);
    dbkey->data[0] = certDBEntryTypeCert;

    return (SECSuccess);
loser:
    return (SECFailure);
}

static SECStatus
EncodeDBGenericKey(const SECItem *certKey, PLArenaPool *arena, SECItem *dbkey,
                   certDBEntryType entryType)
{
    /*
     * we only allow _one_ KRL key!
     */

    if (entryType == certDBEntryTypeKeyRevocation) {
        dbkey->len = SEC_DB_KEY_HEADER_LEN;
        dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
        if (dbkey->data == NULL) {
            goto loser;
        }
        dbkey->data[0] = (unsigned char)entryType;
        return (SECSuccess);
    }

    dbkey->len = certKey->len + SEC_DB_KEY_HEADER_LEN;
    if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE)
        goto loser;
    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
    if (dbkey->data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN],
                certKey->data, certKey->len);
    dbkey->data[0] = (unsigned char)entryType;

    return (SECSuccess);
loser:
    return (SECFailure);
}

static SECStatus
DecodeDBCertEntry(certDBEntryCert *entry, SECItem *dbentry)
{
    unsigned int nnlen;
    unsigned int headerlen;
    int lenoff;

    /* allow updates of old versions of the database */
    switch (entry->common.version) {
        case 5:
            headerlen = DB_CERT_V5_ENTRY_HEADER_LEN;
            lenoff = 3;
            break;
        case 6:
            /* should not get here */
            PORT_Assert(0);
            headerlen = DB_CERT_V6_ENTRY_HEADER_LEN;
            lenoff = 3;
            break;
        case 7:
        case 8:
            headerlen = DB_CERT_ENTRY_HEADER_LEN;
            lenoff = 6;
            break;
        default:
            /* better not get here */
            PORT_Assert(0);
            headerlen = DB_CERT_V5_ENTRY_HEADER_LEN;
            lenoff = 3;
            break;
    }

    /* is record long enough for header? */
    if (dbentry->len < headerlen) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* is database entry correct length? */
    entry->derCert.len = ((dbentry->data[lenoff] << 8) |
                          dbentry->data[lenoff + 1]);
    nnlen = ((dbentry->data[lenoff + 2] << 8) | dbentry->data[lenoff + 3]);
    lenoff = dbentry->len - (entry->derCert.len + nnlen + headerlen);
    if (lenoff) {
        if (lenoff < 0 || (lenoff & 0xffff) != 0) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            goto loser;
        }
        /* The cert size exceeded 64KB.  Reconstruct the correct length. */
        entry->derCert.len += lenoff;
    }

    /* Is data long enough? */
    if (dbentry->len < headerlen + entry->derCert.len) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* copy the dercert */
    entry->derCert.data = pkcs11_copyStaticData(&dbentry->data[headerlen],
                                                entry->derCert.len, entry->derCertSpace, sizeof(entry->derCertSpace));
    if (entry->derCert.data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* copy the nickname */
    if (nnlen > 1) {
        /* Is data long enough? */
        if (dbentry->len < headerlen + entry->derCert.len + nnlen) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            goto loser;
        }
        entry->nickname = (char *)pkcs11_copyStaticData(
            &dbentry->data[headerlen + entry->derCert.len], nnlen,
            (unsigned char *)entry->nicknameSpace,
            sizeof(entry->nicknameSpace));
        if (entry->nickname == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
    } else {
        entry->nickname = NULL;
    }

    if (entry->common.version < 7) {
        /* allow updates of v5 db */
        entry->trust.sslFlags = dbentry->data[0];
        entry->trust.emailFlags = dbentry->data[1];
        entry->trust.objectSigningFlags = dbentry->data[2];
    } else {
        entry->trust.sslFlags = (dbentry->data[0] << 8) | dbentry->data[1];
        entry->trust.emailFlags = (dbentry->data[2] << 8) | dbentry->data[3];
        entry->trust.objectSigningFlags =
            (dbentry->data[4] << 8) | dbentry->data[5];
    }

    return (SECSuccess);
loser:
    return (SECFailure);
}

/*
 * Create a new certDBEntryCert from existing data
 */

static certDBEntryCert *
NewDBCertEntry(SECItem *derCert, char *nickname,
               NSSLOWCERTCertTrust *trust, int flags)
{
    certDBEntryCert *entry;
    PLArenaPool *arena = NULL;
    int nnlen;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        goto loser;
    }

    entry = PORT_ArenaZNew(arena, certDBEntryCert);
    if (entry == NULL) {
        goto loser;
    }

    /* fill in the dbCert */
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeCert;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    if (trust) {
        entry->trust = *trust;
    }

    entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, derCert->len);
    if (!entry->derCert.data) {
        goto loser;
    }
    entry->derCert.len = derCert->len;
    PORT_Memcpy(entry->derCert.data, derCert->data, derCert->len);

    nnlen = (nickname ? strlen(nickname) + 1 : 0);

    if (nnlen) {
        entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
        if (!entry->nickname) {
            goto loser;
        }
        PORT_Memcpy(entry->nickname, nickname, nnlen);

    } else {
        entry->nickname = 0;
    }

    return (entry);

loser:

    /* allocation error, free arena and return */
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    PORT_SetError(SEC_ERROR_NO_MEMORY);
    return (0);
}

/*
 * Decode a version 4 DBCert from the byte stream database format
 * and construct a current database entry struct
 */

static certDBEntryCert *
DecodeV4DBCertEntry(unsigned char *buf, int len)
{
    certDBEntryCert *entry;
    int certlen;
    int nnlen;
    PLArenaPool *arena;

    /* make sure length is at least long enough for the header */
    if (len < DBCERT_V4_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        return (0);
    }

    /* get other lengths */
    certlen = buf[3] << 8 | buf[4];
    nnlen = buf[5] << 8 | buf[6];

    /* make sure DB entry is the right size */
    if ((certlen + nnlen + DBCERT_V4_HEADER_LEN) != len) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        return (0);
    }

    /* allocate arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return (0);
    }

    /* allocate structure and members */
    entry = (certDBEntryCert *)PORT_ArenaAlloc(arena, sizeof(certDBEntryCert));

    if (!entry) {
        goto loser;
    }

    entry->common.arena = arena;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.type = certDBEntryTypeCert;
    entry->common.flags = 0;
    entry->trust.sslFlags = buf[0];
    entry->trust.emailFlags = buf[1];
    entry->trust.objectSigningFlags = buf[2];

    entry->derCert.data = (unsigned char *)PORT_ArenaAlloc(arena, certlen);
    if (!entry->derCert.data) {
        goto loser;
    }
    entry->derCert.len = certlen;
    PORT_Memcpy(entry->derCert.data, &buf[DBCERT_V4_HEADER_LEN], certlen);

    if (nnlen) {
        entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
        if (!entry->nickname) {
            goto loser;
        }
        PORT_Memcpy(entry->nickname, &buf[DBCERT_V4_HEADER_LEN + certlen], nnlen);

        if (PORT_Strcmp(entry->nickname, "Server-Cert") == 0) {
            entry->trust.sslFlags |= CERTDB_USER;
        }
    } else {
        entry->nickname = 0;
    }

    return (entry);

loser:
    PORT_FreeArena(arena, PR_FALSE);
    PORT_SetError(SEC_ERROR_NO_MEMORY);
    return (0);
}

/*
 * Encode a Certificate database entry into byte stream suitable for
 * the database
 */

static SECStatus
WriteDBCertEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry)
{
    SECItem dbitem, dbkey;
    PLArenaPool *tmparena = NULL;
    SECItem tmpitem;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    rv = EncodeDBCertEntry(entry, tmparena, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* get the database key and format it */
    rv = nsslowcert_KeyFromDERCert(tmparena, &entry->derCert, &tmpitem);
    if (rv == SECFailure) {
        goto loser;
    }

    rv = EncodeDBCertKey(&tmpitem, tmparena, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}

/*
 * delete a certificate entry
 */

static SECStatus
DeleteDBCertEntry(NSSLOWCERTCertDBHandle *handle, SECItem *certKey)
{
    SECItem dbkey;
    SECStatus rv;

    dbkey.data = NULL;
    dbkey.len = 0;

    rv = EncodeDBCertKey(certKey, NULL, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = DeleteDBEntry(handle, certDBEntryTypeCert, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_Free(dbkey.data);

    return (SECSuccess);

loser:
    if (dbkey.data) {
        PORT_Free(dbkey.data);
    }
    return (SECFailure);
}

static certDBEntryCert *
CreateCertEntry(void)
{
    certDBEntryCert *entry;

    nsslowcert_LockFreeList();
    entry = entryListHead;
    if (entry) {
        entryListCount--;
        entryListHead = entry->next;
    }
    PORT_Assert(entryListCount >= 0);
    nsslowcert_UnlockFreeList();
    if (entry) {
        return entry;
    }

    return PORT_ZNew(certDBEntryCert);
}

static void
DestroyCertEntryFreeList(void)
{
    certDBEntryCert *entry;

    nsslowcert_LockFreeList();
    while (NULL != (entry = entryListHead)) {
        entryListCount--;
        entryListHead = entry->next;
        PORT_Free(entry);
    }
    PORT_Assert(!entryListCount);
    entryListCount = 0;
    nsslowcert_UnlockFreeList();
}

/*
 * Read a certificate entry
 */

static certDBEntryCert *
ReadDBCertEntry(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey)
{
    certDBEntryCert *entry;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;
    unsigned char buf[512];

    dbkey.data = buf;
    dbkey.len = sizeof(buf);

    entry = CreateCertEntry();
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = NULL;
    entry->common.type = certDBEntryTypeCert;

    rv = EncodeDBCertKey(certKey, NULL, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, NULL);
    if (rv == SECFailure) {
        goto loser;
    }

    rv = DecodeDBCertEntry(entry, &dbentry);
    if (rv != SECSuccess) {
        goto loser;
    }

    pkcs11_freeStaticData(dbkey.data, buf);
    dbkey.data = NULL;
    return (entry);

loser:
    pkcs11_freeStaticData(dbkey.data, buf);
    dbkey.data = NULL;
    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }

    return (NULL);
}

/*
 * encode a database cert record
 */

static SECStatus
EncodeDBCrlEntry(certDBEntryRevocation *entry, PLArenaPool *arena, SECItem *dbitem)
{
    unsigned int nnlen = 0;
    unsigned char *buf;

    if (entry->url) {
        nnlen = PORT_Strlen(entry->url) + 1;
    }

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem->len = entry->derCrl.len + nnlen + SEC_DB_ENTRY_HEADER_LEN + DB_CRL_ENTRY_HEADER_LEN;

    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
    if (dbitem->data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* fill in database record */
    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];

    buf[0] = (PRUint8)(entry->derCrl.len >> 8);
    buf[1] = (PRUint8)(entry->derCrl.len);
    buf[2] = (PRUint8)(nnlen >> 8);
    buf[3] = (PRUint8)(nnlen);

    PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN], entry->derCrl.data,
                entry->derCrl.len);

    if (nnlen != 0) {
        PORT_Memcpy(&buf[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len],
                    entry->url, nnlen);
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

static SECStatus
DecodeDBCrlEntry(certDBEntryRevocation *entry, SECItem *dbentry)
{
    unsigned int urlLen;
    int lenDiff;

    /* is record long enough for header? */
    if (dbentry->len < DB_CRL_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* is database entry correct length? */
    entry->derCrl.len = ((dbentry->data[0] << 8) | dbentry->data[1]);
    urlLen = ((dbentry->data[2] << 8) | dbentry->data[3]);
    lenDiff = dbentry->len -
              (entry->derCrl.len + urlLen + DB_CRL_ENTRY_HEADER_LEN);
    if (lenDiff) {
        if (lenDiff < 0 || (lenDiff & 0xffff) != 0) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            goto loser;
        }
        /* CRL entry is greater than 64 K. Hack to make this continue to work */
        entry->derCrl.len += lenDiff;
    }

    /* copy the der CRL */
    entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
                                                          entry->derCrl.len);
    if (entry->derCrl.data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    PORT_Memcpy(entry->derCrl.data, &dbentry->data[DB_CRL_ENTRY_HEADER_LEN],
                entry->derCrl.len);

    /* copy the url */
    entry->url = NULL;
    if (urlLen != 0) {
        entry->url = (char *)PORT_ArenaAlloc(entry->common.arena, urlLen);
        if (entry->url == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->url,
                    &dbentry->data[DB_CRL_ENTRY_HEADER_LEN + entry->derCrl.len],
                    urlLen);
    }

    return (SECSuccess);
loser:
    return (SECFailure);
}

/*
 * Create a new certDBEntryRevocation from existing data
 */

static certDBEntryRevocation *
NewDBCrlEntry(SECItem *derCrl, char *url, certDBEntryType crlType, int flags)
{
    certDBEntryRevocation *entry;
    PLArenaPool *arena = NULL;
    int nnlen;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);

    if (!arena) {
        goto loser;
    }

    entry = PORT_ArenaZNew(arena, certDBEntryRevocation);
    if (entry == NULL) {
        goto loser;
    }

    /* fill in the dbRevolcation */
    entry->common.arena = arena;
    entry->common.type = crlType;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    entry->derCrl.data = (unsigned char *)PORT_ArenaAlloc(arena, derCrl->len);
    if (!entry->derCrl.data) {
        goto loser;
    }

    if (url) {
        nnlen = PORT_Strlen(url) + 1;
        entry->url = (char *)PORT_ArenaAlloc(arena, nnlen);
        if (!entry->url) {
            goto loser;
        }
        PORT_Memcpy(entry->url, url, nnlen);
    } else {
        entry->url = NULL;
    }

    entry->derCrl.len = derCrl->len;
    PORT_Memcpy(entry->derCrl.data, derCrl->data, derCrl->len);

    return (entry);

loser:

    /* allocation error, free arena and return */
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    PORT_SetError(SEC_ERROR_NO_MEMORY);
    return (0);
}

static SECStatus
WriteDBCrlEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryRevocation *entry,
                SECItem *crlKey)
{
    SECItem dbkey;
    PLArenaPool *tmparena = NULL;
    SECItem encodedEntry;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    rv = EncodeDBCrlEntry(entry, tmparena, &encodedEntry);
    if (rv == SECFailure) {
        goto loser;
    }

    rv = EncodeDBGenericKey(crlKey, tmparena, &dbkey, entry->common.type);
    if (rv == SECFailure) {
        goto loser;
    }

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &encodedEntry);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}
/*
 * delete a crl entry
 */

static SECStatus
DeleteDBCrlEntry(NSSLOWCERTCertDBHandle *handle, const SECItem *crlKey,
                 certDBEntryType crlType)
{
    SECItem dbkey;
    PLArenaPool *arena = NULL;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    rv = EncodeDBGenericKey(crlKey, arena, &dbkey, crlType);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = DeleteDBEntry(handle, crlType, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_FreeArena(arena, PR_FALSE);
    return (SECSuccess);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (SECFailure);
}

/*
 * Read a certificate entry
 */

static certDBEntryRevocation *
ReadDBCrlEntry(NSSLOWCERTCertDBHandle *handle, SECItem *certKey,
               certDBEntryType crlType)
{
    PLArenaPool *arena = NULL;
    PLArenaPool *tmparena = NULL;
    certDBEntryRevocation *entry;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntryRevocation *)
        PORT_ArenaAlloc(arena, sizeof(certDBEntryRevocation));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = crlType;

    rv = EncodeDBGenericKey(certKey, tmparena, &dbkey, crlType);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, NULL);
    if (rv == SECFailure) {
        goto loser;
    }

    rv = DecodeDBCrlEntry(entry, &dbentry);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (entry);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

void
nsslowcert_DestroyDBEntry(certDBEntry *entry)
{
    DestroyDBEntry(entry);
    return;
}

/*
 * Encode a database nickname record
 */

static SECStatus
EncodeDBNicknameEntry(certDBEntryNickname *entry, PLArenaPool *arena,
                      SECItem *dbitem)
{
    unsigned char *buf;

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem->len = entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN +
                  SEC_DB_ENTRY_HEADER_LEN;
    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
    if (dbitem->data == NULL) {
        goto loser;
    }

    /* fill in database record */
    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];
    buf[0] = (PRUint8)(entry->subjectName.len >> 8);
    buf[1] = (PRUint8)(entry->subjectName.len);
    PORT_Memcpy(&buf[DB_NICKNAME_ENTRY_HEADER_LEN], entry->subjectName.data,
                entry->subjectName.len);

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * Encode a database key for a nickname record
 */

static SECStatus
EncodeDBNicknameKey(char *nickname, PLArenaPool *arena,
                    SECItem *dbkey)
{
    unsigned int nnlen;

    nnlen = PORT_Strlen(nickname) + 1; /* includes null */

    /* now get the database key and format it */
    dbkey->len = nnlen + SEC_DB_KEY_HEADER_LEN;
    if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE)
        goto loser;
    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
    if (dbkey->data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], nickname, nnlen);
    dbkey->data[0] = certDBEntryTypeNickname;

    return (SECSuccess);

loser:
    return (SECFailure);
}

static SECStatus
DecodeDBNicknameEntry(certDBEntryNickname *entry, SECItem *dbentry,
                      char *nickname)
{
    int lenDiff;

    /* is record long enough for header? */
    if (dbentry->len < DB_NICKNAME_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* is database entry correct length? */
    entry->subjectName.len = ((dbentry->data[0] << 8) | dbentry->data[1]);
    lenDiff = dbentry->len -
              (entry->subjectName.len + DB_NICKNAME_ENTRY_HEADER_LEN);
    if (lenDiff) {
        if (lenDiff < 0 || (lenDiff & 0xffff) != 0) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            goto loser;
        }
        /* The entry size exceeded 64KB.  Reconstruct the correct length. */
        entry->subjectName.len += lenDiff;
    }

    /* copy the certkey */
    entry->subjectName.data =
        (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
                                         entry->subjectName.len);
    if (entry->subjectName.data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    PORT_Memcpy(entry->subjectName.data,
                &dbentry->data[DB_NICKNAME_ENTRY_HEADER_LEN],
                entry->subjectName.len);
    entry->subjectName.type = siBuffer;

    entry->nickname = (char *)PORT_ArenaAlloc(entry->common.arena,
                                              PORT_Strlen(nickname) + 1);
    if (entry->nickname) {
        PORT_Strcpy(entry->nickname, nickname);
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * create a new nickname entry
 */

static certDBEntryNickname *
NewDBNicknameEntry(char *nickname, SECItem *subjectName, unsigned int flags)
{
    PLArenaPool *arena = NULL;
    certDBEntryNickname *entry;
    int nnlen;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena,
                                                   sizeof(certDBEntryNickname));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* init common fields */
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeNickname;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    /* copy the nickname */
    nnlen = PORT_Strlen(nickname) + 1;

    entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
    if (entry->nickname == NULL) {
        goto loser;
    }

    PORT_Memcpy(entry->nickname, nickname, nnlen);

    rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName);
    if (rv != SECSuccess) {
        goto loser;
    }

    return (entry);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * delete a nickname entry
 */

static SECStatus
DeleteDBNicknameEntry(NSSLOWCERTCertDBHandle *handle, char *nickname)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    SECItem dbkey;

    if (nickname == NULL) {
        return (SECSuccess);
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    rv = EncodeDBNicknameKey(nickname, arena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = DeleteDBEntry(handle, certDBEntryTypeNickname, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_FreeArena(arena, PR_FALSE);
    return (SECSuccess);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (SECFailure);
}

/*
 * Read a nickname entry
 */

static certDBEntryNickname *
ReadDBNicknameEntry(NSSLOWCERTCertDBHandle *handle, char *nickname)
{
    PLArenaPool *arena = NULL;
    PLArenaPool *tmparena = NULL;
    certDBEntryNickname *entry;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntryNickname *)PORT_ArenaAlloc(arena,
                                                   sizeof(certDBEntryNickname));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeNickname;

    rv = EncodeDBNicknameKey(nickname, tmparena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
    if (rv == SECFailure) {
        goto loser;
    }

    /* is record long enough for header? */
    if (dbentry.len < DB_NICKNAME_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    rv = DecodeDBNicknameEntry(entry, &dbentry, nickname);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (entry);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * Encode a nickname entry into byte stream suitable for
 * the database
 */

static SECStatus
WriteDBNicknameEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryNickname *entry)
{
    SECItem dbitem, dbkey;
    PLArenaPool *tmparena = NULL;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    rv = EncodeDBNicknameEntry(entry, tmparena, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = EncodeDBNicknameKey(entry->nickname, tmparena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}

static SECStatus
EncodeDBSMimeEntry(certDBEntrySMime *entry, PLArenaPool *arena,
                   SECItem *dbitem)
{
    unsigned char *buf;

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem->len = entry->subjectName.len + entry->smimeOptions.len +
                  entry->optionsDate.len +
                  DB_SMIME_ENTRY_HEADER_LEN + SEC_DB_ENTRY_HEADER_LEN;

    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
    if (dbitem->data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* fill in database record */
    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];

    buf[0] = (PRUint8)(entry->subjectName.len >> 8);
    buf[1] = (PRUint8)(entry->subjectName.len);
    buf[2] = (PRUint8)(entry->smimeOptions.len >> 8);
    buf[3] = (PRUint8)(entry->smimeOptions.len);
    buf[4] = (PRUint8)(entry->optionsDate.len >> 8);
    buf[5] = (PRUint8)(entry->optionsDate.len);

    /* if no smime options, then there should not be an options date either */
    PORT_Assert(!((entry->smimeOptions.len == 0) &&
                  (entry->optionsDate.len != 0)));

    PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN], entry->subjectName.data,
                entry->subjectName.len);
    if (entry->smimeOptions.len) {
        PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN + entry->subjectName.len],
                    entry->smimeOptions.data,
                    entry->smimeOptions.len);
        PORT_Memcpy(&buf[DB_SMIME_ENTRY_HEADER_LEN + entry->subjectName.len +
                         entry->smimeOptions.len],
                    entry->optionsDate.data,
                    entry->optionsDate.len);
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * Encode a database key for a SMIME record
 */

static SECStatus
EncodeDBSMimeKey(char *emailAddr, PLArenaPool *arena,
                 SECItem *dbkey)
{
    unsigned int addrlen;

    addrlen = PORT_Strlen(emailAddr) + 1; /* includes null */

    /* now get the database key and format it */
    dbkey->len = addrlen + SEC_DB_KEY_HEADER_LEN;
    if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE)
        goto loser;
    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
    if (dbkey->data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], emailAddr, addrlen);
    dbkey->data[0] = certDBEntryTypeSMimeProfile;

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * Decode a database SMIME record
 */

static SECStatus
DecodeDBSMimeEntry(certDBEntrySMime *entry, SECItem *dbentry, char *emailAddr)
{
    int lenDiff;

    /* is record long enough for header? */
    if (dbentry->len < DB_SMIME_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    /* is database entry correct length? */
    entry->subjectName.len = ((dbentry->data[0] << 8) | dbentry->data[1]);
    entry->smimeOptions.len = ((dbentry->data[2] << 8) | dbentry->data[3]);
    entry->optionsDate.len = ((dbentry->data[4] << 8) | dbentry->data[5]);
    lenDiff = dbentry->len - (entry->subjectName.len +
                              entry->smimeOptions.len +
                              entry->optionsDate.len +
                              DB_SMIME_ENTRY_HEADER_LEN);
    if (lenDiff) {
        if (lenDiff < 0 || (lenDiff & 0xffff) != 0) {
            PORT_SetError(SEC_ERROR_BAD_DATABASE);
            goto loser;
        }
        /* The entry size exceeded 64KB.  Reconstruct the correct length. */
        entry->subjectName.len += lenDiff;
    }

    /* copy the subject name */
    entry->subjectName.data =
        (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
                                         entry->subjectName.len);
    if (entry->subjectName.data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    PORT_Memcpy(entry->subjectName.data,
                &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN],
                entry->subjectName.len);

    /* copy the smime options */
    if (entry->smimeOptions.len) {
        entry->smimeOptions.data =
            (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
                                             entry->smimeOptions.len);
        if (entry->smimeOptions.data == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->smimeOptions.data,
                    &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN +
                                   entry->subjectName.len],
                    entry->smimeOptions.len);
    } else {
        entry->smimeOptions.data = NULL;
    }
    if (entry->optionsDate.len) {
        entry->optionsDate.data =
            (unsigned char *)PORT_ArenaAlloc(entry->common.arena,
                                             entry->optionsDate.len);
        if (entry->optionsDate.data == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->optionsDate.data,
                    &dbentry->data[DB_SMIME_ENTRY_HEADER_LEN +
                                   entry->subjectName.len +
                                   entry->smimeOptions.len],
                    entry->optionsDate.len);
    } else {
        entry->optionsDate.data = NULL;
    }

    /* both options and options date must either exist or not exist */
    if (((entry->optionsDate.len == 0) ||
         (entry->smimeOptions.len == 0)) &&
        entry->smimeOptions.len != entry->optionsDate.len) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    entry->emailAddr = (char *)PORT_ArenaAlloc(entry->common.arena,
                                               PORT_Strlen(emailAddr) + 1);
    if (entry->emailAddr) {
        PORT_Strcpy(entry->emailAddr, emailAddr);
    }

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * create a new SMIME entry
 */

static certDBEntrySMime *
NewDBSMimeEntry(char *emailAddr, SECItem *subjectName, SECItem *smimeOptions,
                SECItem *optionsDate, unsigned int flags)
{
    PLArenaPool *arena = NULL;
    certDBEntrySMime *entry;
    int addrlen;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntrySMime *)PORT_ArenaAlloc(arena,
                                                sizeof(certDBEntrySMime));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* init common fields */
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeSMimeProfile;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    /* copy the email addr */
    addrlen = PORT_Strlen(emailAddr) + 1;

    entry->emailAddr = (char *)PORT_ArenaAlloc(arena, addrlen);
    if (entry->emailAddr == NULL) {
        goto loser;
    }

    PORT_Memcpy(entry->emailAddr, emailAddr, addrlen);

    /* copy the subject name */
    rv = SECITEM_CopyItem(arena, &entry->subjectName, subjectName);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* copy the smime options */
    if (smimeOptions) {
        rv = SECITEM_CopyItem(arena, &entry->smimeOptions, smimeOptions);
        if (rv != SECSuccess) {
            goto loser;
        }
    } else {
        PORT_Assert(optionsDate == NULL);
        entry->smimeOptions.data = NULL;
        entry->smimeOptions.len = 0;
    }

    /* copy the options date */
    if (optionsDate) {
        rv = SECITEM_CopyItem(arena, &entry->optionsDate, optionsDate);
        if (rv != SECSuccess) {
            goto loser;
        }
    } else {
        PORT_Assert(smimeOptions == NULL);
        entry->optionsDate.data = NULL;
        entry->optionsDate.len = 0;
    }

    return (entry);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * delete a SMIME entry
 */

static SECStatus
DeleteDBSMimeEntry(NSSLOWCERTCertDBHandle *handle, char *emailAddr)
{
    PLArenaPool *arena = NULL;
    SECStatus rv;
    SECItem dbkey;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    rv = EncodeDBSMimeKey(emailAddr, arena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = DeleteDBEntry(handle, certDBEntryTypeSMimeProfile, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_FreeArena(arena, PR_FALSE);
    return (SECSuccess);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (SECFailure);
}

/*
 * Read a SMIME entry
 */

certDBEntrySMime *
nsslowcert_ReadDBSMimeEntry(NSSLOWCERTCertDBHandle *handle, char *emailAddr)
{
    PLArenaPool *arena = NULL;
    PLArenaPool *tmparena = NULL;
    certDBEntrySMime *entry = NULL;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntrySMime *)PORT_ArenaZAlloc(arena,
                                                 sizeof(certDBEntrySMime));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeSMimeProfile;

    rv = EncodeDBSMimeKey(emailAddr, tmparena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
    if (rv == SECFailure) {
        goto loser;
    }

    /* is record long enough for header? */
    if (dbentry.len < DB_SMIME_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    rv = DecodeDBSMimeEntry(entry, &dbentry, emailAddr);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (entry);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * Encode a SMIME entry into byte stream suitable for
 * the database
 */

static SECStatus
WriteDBSMimeEntry(NSSLOWCERTCertDBHandle *handle, certDBEntrySMime *entry)
{
    SECItem dbitem, dbkey;
    PLArenaPool *tmparena = NULL;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    rv = EncodeDBSMimeEntry(entry, tmparena, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = EncodeDBSMimeKey(entry->emailAddr, tmparena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}

/*
 * Encode a database subject record
 */

static SECStatus
EncodeDBSubjectEntry(certDBEntrySubject *entry, PLArenaPool *arena,
                     SECItem *dbitem)
{
    unsigned char *buf;
    int len;
    unsigned int ncerts;
    unsigned int i;
    unsigned char *tmpbuf;
    unsigned int nnlen = 0;
    unsigned int eaddrslen = 0;
    int keyidoff;
    SECItem *certKeys = entry->certKeys;
    SECItem *keyIDs = entry->keyIDs;
    ;

    if (entry->nickname) {
        nnlen = PORT_Strlen(entry->nickname) + 1;
    }
    if (entry->emailAddrs) {
        eaddrslen = 2;
        for (i = 0; i < entry->nemailAddrs; i++) {
            eaddrslen += PORT_Strlen(entry->emailAddrs[i]) + 1 + 2;
        }
    }

    ncerts = entry->ncerts;

    /* compute the length of the entry */
    keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen;
    len = keyidoff + (4 * ncerts) + eaddrslen;
    for (i = 0; i < ncerts; i++) {
        if (keyIDs[i].len > 0xffff ||
            (certKeys[i].len > 0xffff)) {
            PORT_SetError(SEC_ERROR_INPUT_LEN);
            goto loser;
        }
        len += certKeys[i].len;
        len += keyIDs[i].len;
    }

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem->len = len + SEC_DB_ENTRY_HEADER_LEN;

    dbitem->data = (unsigned char *)PORT_ArenaAlloc(arena, dbitem->len);
    if (dbitem->data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* fill in database record */
    buf = &dbitem->data[SEC_DB_ENTRY_HEADER_LEN];

    buf[0] = (PRUint8)(ncerts >> 8);
    buf[1] = (PRUint8)(ncerts);
    buf[2] = (PRUint8)(nnlen >> 8);
    buf[3] = (PRUint8)(nnlen);
    /* v7 email field is NULL in v8 */
    buf[4] = 0;
    buf[5] = 0;

    PORT_Assert(DB_SUBJECT_ENTRY_HEADER_LEN == 6);

    if (entry->nickname) {
        PORT_Memcpy(&buf[DB_SUBJECT_ENTRY_HEADER_LEN], entry->nickname, nnlen);
    }
    tmpbuf = &buf[keyidoff];
    for (i = 0; i < ncerts; i++) {
        tmpbuf[0] = (PRUint8)(certKeys[i].len >> 8);
        tmpbuf[1] = (PRUint8)(certKeys[i].len);
        tmpbuf += 2;
    }
    for (i = 0; i < ncerts; i++) {
        tmpbuf[0] = (PRUint8)(keyIDs[i].len >> 8);
        tmpbuf[1] = (PRUint8)(keyIDs[i].len);
        tmpbuf += 2;
    }

    for (i = 0; i < ncerts; i++) {
        PORT_Memcpy(tmpbuf, certKeys[i].data, certKeys[i].len);
        tmpbuf += certKeys[i].len;
    }
    for (i = 0; i < ncerts; i++) {
        if (keyIDs[i].len) {
            PORT_Memcpy(tmpbuf, keyIDs[i].data, keyIDs[i].len);
            tmpbuf += keyIDs[i].len;
        }
    }

    if (entry->emailAddrs) {
        tmpbuf[0] = (PRUint8)(entry->nemailAddrs >> 8);
        tmpbuf[1] = (PRUint8)(entry->nemailAddrs);
        tmpbuf += 2;
        for (i = 0; i < entry->nemailAddrs; i++) {
            int nameLen = PORT_Strlen(entry->emailAddrs[i]) + 1;
            tmpbuf[0] = (PRUint8)(nameLen >> 8);
            tmpbuf[1] = (PRUint8)(nameLen);
            tmpbuf += 2;
            PORT_Memcpy(tmpbuf, entry->emailAddrs[i], nameLen);
            tmpbuf += nameLen;
        }
    }

    PORT_Assert(tmpbuf == &buf[len]);

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * Encode a database key for a subject record
 */

static SECStatus
EncodeDBSubjectKey(SECItem *derSubject, PLArenaPool *arena,
                   SECItem *dbkey)
{
    dbkey->len = derSubject->len + SEC_DB_KEY_HEADER_LEN;
    if (dbkey->len > NSS_MAX_LEGACY_DB_KEY_SIZE)
        goto loser;
    dbkey->data = (unsigned char *)PORT_ArenaAlloc(arena, dbkey->len);
    if (dbkey->data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey->data[SEC_DB_KEY_HEADER_LEN], derSubject->data,
                derSubject->len);
    dbkey->data[0] = certDBEntryTypeSubject;

    return (SECSuccess);

loser:
    return (SECFailure);
}

static SECStatus
DecodeDBSubjectEntry(certDBEntrySubject *entry, SECItem *dbentry,
                     const SECItem *derSubject)
{
    PLArenaPool *arena = entry->common.arena;
    unsigned char *tmpbuf;
    unsigned char *end;
    void *mark = PORT_ArenaMark(arena);
    unsigned int eaddrlen;
    unsigned int i;
    unsigned int keyidoff;
    unsigned int len;
    unsigned int ncerts = 0;
    unsigned int nnlen;
    SECStatus rv;

    rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* is record long enough for header? */
    if (dbentry->len < DB_SUBJECT_ENTRY_HEADER_LEN) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    entry->ncerts = ncerts = ((dbentry->data[0] << 8) | dbentry->data[1]);
    nnlen = ((dbentry->data[2] << 8) | dbentry->data[3]);
    eaddrlen = ((dbentry->data[4] << 8) | dbentry->data[5]);
    keyidoff = DB_SUBJECT_ENTRY_HEADER_LEN + nnlen + eaddrlen;
    len = keyidoff + (4 * ncerts);
    if (dbentry->len < len) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    entry->certKeys = PORT_ArenaNewArray(arena, SECItem, ncerts);
    entry->keyIDs = PORT_ArenaNewArray(arena, SECItem, ncerts);
    if ((entry->certKeys == NULL) || (entry->keyIDs == NULL)) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    if (nnlen > 1) { /* null terminator is stored */
        entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
        if (entry->nickname == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->nickname,
                    &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN],
                    nnlen);
    } else {
        entry->nickname = NULL;
    }

    /* if we have an old style email entry, there is only one */
    entry->nemailAddrs = 0;
    if (eaddrlen > 1) { /* null terminator is stored */
        entry->emailAddrs = PORT_ArenaNewArray(arena, char *, 2);
        if (entry->emailAddrs == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        entry->emailAddrs[0] = (char *)PORT_ArenaAlloc(arena, eaddrlen);
        if (entry->emailAddrs[0] == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->emailAddrs[0],
                    &dbentry->data[DB_SUBJECT_ENTRY_HEADER_LEN + nnlen],
                    eaddrlen);
        entry->nemailAddrs = 1;
    } else {
        entry->emailAddrs = NULL;
    }

    /* collect the lengths of the certKeys and keyIDs, and total the
     * overall length.
     */

    tmpbuf = &dbentry->data[keyidoff];
    for (i = 0; i < ncerts; i++) {
        unsigned int itemlen = (tmpbuf[0] << 8) | tmpbuf[1];
        entry->certKeys[i].len = itemlen;
        len += itemlen;
        tmpbuf += 2;
    }
    for (i = 0; i < ncerts; i++) {
        unsigned int itemlen = (tmpbuf[0] << 8) | tmpbuf[1];
        entry->keyIDs[i].len = itemlen;
        len += itemlen;
        tmpbuf += 2;
    }

    /* is encoded entry large enough ? */
    if (len > dbentry->len) {
        PORT_SetError(SEC_ERROR_BAD_DATABASE);
        goto loser;
    }

    for (i = 0; i < ncerts; i++) {
        unsigned int kLen = entry->certKeys[i].len;
        entry->certKeys[i].data = (unsigned char *)PORT_ArenaAlloc(arena, kLen);
        if (entry->certKeys[i].data == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->certKeys[i].data, tmpbuf, kLen);
        tmpbuf += kLen;
    }
    for (i = 0; i < ncerts; i++) {
        unsigned int iLen = entry->keyIDs[i].len;
        entry->keyIDs[i].data = (unsigned char *)PORT_ArenaAlloc(arena, iLen);
        if (entry->keyIDs[i].data == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        PORT_Memcpy(entry->keyIDs[i].data, tmpbuf, iLen);
        tmpbuf += iLen;
    }

    end = dbentry->data + dbentry->len;
    if ((eaddrlen == 0) && (end - tmpbuf > 1)) {
        /* read in the additional email addresses */
        entry->nemailAddrs = (((unsigned int)tmpbuf[0]) << 8) | tmpbuf[1];
        tmpbuf += 2;
        if (end - tmpbuf < 2 * (int)entry->nemailAddrs)
            goto loser;
        entry->emailAddrs = PORT_ArenaNewArray(arena, char *, entry->nemailAddrs);
        if (entry->emailAddrs == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        for (i = 0; i < entry->nemailAddrs; i++) {
            int nameLen;
            if (end - tmpbuf < 2) {
                goto loser;
            }
            nameLen = (((int)tmpbuf[0]) << 8) | tmpbuf[1];
            tmpbuf += 2;
            if (end - tmpbuf < nameLen) {
                goto loser;
            }
            entry->emailAddrs[i] = PORT_ArenaAlloc(arena, nameLen);
            if (entry->emailAddrs == NULL) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }
            PORT_Memcpy(entry->emailAddrs[i], tmpbuf, nameLen);
            tmpbuf += nameLen;
        }
        if (tmpbuf != end)
            goto loser;
    }
    PORT_ArenaUnmark(arena, mark);
    return (SECSuccess);

loser:
    PORT_ArenaRelease(arena, mark); /* discard above allocations */
    return (SECFailure);
}

/*
 * create a new subject entry with a single cert
 */

static certDBEntrySubject *
NewDBSubjectEntry(SECItem *derSubject, SECItem *certKey,
                  SECItem *keyID, char *nickname, char *emailAddr,
                  unsigned int flags)
{
    PLArenaPool *arena = NULL;
    certDBEntrySubject *entry;
    SECStatus rv;
    unsigned int nnlen;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena,
                                                  sizeof(certDBEntrySubject));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* init common fields */
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeSubject;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    /* copy the subject */
    rv = SECITEM_CopyItem(arena, &entry->derSubject, derSubject);
    if (rv != SECSuccess) {
        goto loser;
    }

    entry->ncerts = 1;
    entry->nemailAddrs = 0;
    /* copy nickname */
    if (nickname && (*nickname != '\0')) {
        nnlen = PORT_Strlen(nickname) + 1;
        entry->nickname = (char *)PORT_ArenaAlloc(arena, nnlen);
        if (entry->nickname == NULL) {
            goto loser;
        }

        PORT_Memcpy(entry->nickname, nickname, nnlen);
    } else {
        entry->nickname = NULL;
    }

    /* copy email addr */
    if (emailAddr && (*emailAddr != '\0')) {
        emailAddr = nsslowcert_FixupEmailAddr(emailAddr);
        if (emailAddr == NULL) {
            entry->emailAddrs = NULL;
            goto loser;
        }

        entry->emailAddrs = (char **)PORT_ArenaAlloc(arena, sizeof(char *));
        if (entry->emailAddrs == NULL) {
            PORT_Free(emailAddr);
            goto loser;
        }
        entry->emailAddrs[0] = PORT_ArenaStrdup(arena, emailAddr);
        if (entry->emailAddrs[0]) {
            entry->nemailAddrs = 1;
        }

        PORT_Free(emailAddr);
    } else {
        entry->emailAddrs = NULL;
    }

    /* allocate space for certKeys and keyIDs */
    entry->certKeys = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem));
    entry->keyIDs = (SECItem *)PORT_ArenaAlloc(arena, sizeof(SECItem));
    if ((entry->certKeys == NULL) || (entry->keyIDs == NULL)) {
        goto loser;
    }

    /* copy the certKey and keyID */
    rv = SECITEM_CopyItem(arena, &entry->certKeys[0], certKey);
    if (rv != SECSuccess) {
        goto loser;
    }
    rv = SECITEM_CopyItem(arena, &entry->keyIDs[0], keyID);
    if (rv != SECSuccess) {
        goto loser;
    }

    return (entry);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * delete a subject entry
 */

static SECStatus
DeleteDBSubjectEntry(NSSLOWCERTCertDBHandle *handle, SECItem *derSubject)
{
    SECItem dbkey;
    PLArenaPool *arena = NULL;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    rv = EncodeDBSubjectKey(derSubject, arena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = DeleteDBEntry(handle, certDBEntryTypeSubject, &dbkey);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_FreeArena(arena, PR_FALSE);
    return (SECSuccess);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (SECFailure);
}

/*
 * Read the subject entry
 */

static certDBEntrySubject *
ReadDBSubjectEntry(NSSLOWCERTCertDBHandle *handle, SECItem *derSubject)
{
    /* |arena| isn't function-bounded, so cannot be a PORTCheapArenaPool. */
    PLArenaPool *arena = NULL;
    PORTCheapArenaPool tmpArena;

    certDBEntrySubject *entry;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;

    PORT_InitCheapArena(&tmpArena, DER_DEFAULT_CHUNKSIZE);
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntrySubject *)PORT_ArenaAlloc(arena,
                                                  sizeof(certDBEntrySubject));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeSubject;

    rv = EncodeDBSubjectKey(derSubject, &tmpArena.arena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, &tmpArena.arena);
    if (rv == SECFailure) {
        goto loser;
    }

    rv = DecodeDBSubjectEntry(entry, &dbentry, derSubject);
    if (rv == SECFailure) {
        goto loser;
    }

    PORT_DestroyCheapArena(&tmpArena);
    return (entry);

loser:
    PORT_DestroyCheapArena(&tmpArena);
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * Encode a subject name entry into byte stream suitable for
 * the database
 */

static SECStatus
WriteDBSubjectEntry(NSSLOWCERTCertDBHandle *handle, certDBEntrySubject *entry)
{
    SECItem dbitem, dbkey;
    PLArenaPool *tmparena = NULL;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    rv = EncodeDBSubjectEntry(entry, tmparena, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = EncodeDBSubjectKey(&entry->derSubject, tmparena, &dbkey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}

typedef enum { nsslowcert_remove,
               nsslowcert_add } nsslowcertUpdateType;

static SECStatus
nsslowcert_UpdateSubjectEmailAddr(NSSLOWCERTCertDBHandle *dbhandle,
                                  SECItem *derSubject, char *emailAddr, nsslowcertUpdateType updateType)
{
    certDBEntrySubject *entry = NULL;
    int index = -1, i;
    SECStatus rv;

    if (emailAddr) {
        emailAddr = nsslowcert_FixupEmailAddr(emailAddr);
        if (emailAddr == NULL) {
            return SECFailure;
        }
    } else {
        return SECSuccess;
    }

    entry = ReadDBSubjectEntry(dbhandle, derSubject);
    if (entry == NULL) {
        rv = SECFailure;
        goto done;
    }

    for (i = 0; i < (int)(entry->nemailAddrs); i++) {
        if (PORT_Strcmp(entry->emailAddrs[i], emailAddr) == 0) {
            index = i;
        }
    }

    if (updateType == nsslowcert_remove) {
        if (index == -1) {
            rv = SECSuccess;
            goto done;
        }
        entry->nemailAddrs--;
        for (i = index; i < (int)(entry->nemailAddrs); i++) {
            entry->emailAddrs[i] = entry->emailAddrs[i + 1];
        }
    } else {
        char **newAddrs = NULL;

        if (index != -1) {
            rv = SECSuccess;
            goto done;
        }
        newAddrs = (char **)PORT_ArenaAlloc(entry->common.arena,
                                            (entry->nemailAddrs + 1) * sizeof(char *));
        if (!newAddrs) {
            rv = SECFailure;
            goto done;
        }
        for (i = 0; i < (int)(entry->nemailAddrs); i++) {
            newAddrs[i] = entry->emailAddrs[i];
        }
        newAddrs[entry->nemailAddrs] =
            PORT_ArenaStrdup(entry->common.arena, emailAddr);
        if (!newAddrs[entry->nemailAddrs]) {
            rv = SECFailure;
            goto done;
        }
        entry->emailAddrs = newAddrs;
        entry->nemailAddrs++;
    }

    /* delete the subject entry */
    DeleteDBSubjectEntry(dbhandle, derSubject);

    /* write the new one */
    rv = WriteDBSubjectEntry(dbhandle, entry);

done:
    if (entry)
        DestroyDBEntry((certDBEntry *)entry);
    if (emailAddr)
        PORT_Free(emailAddr);
    return rv;
}

/*
 * writes a nickname to an existing subject entry that does not currently
 * have one
 */

static SECStatus
AddNicknameToSubject(NSSLOWCERTCertDBHandle *dbhandle,
                     NSSLOWCERTCertificate *cert, char *nickname)
{
    certDBEntrySubject *entry;
    SECStatus rv;

    if (nickname == NULL) {
        return (SECFailure);
    }

    entry = ReadDBSubjectEntry(dbhandle, &cert->derSubject);
    PORT_Assert(entry != NULL);
    if (entry == NULL) {
        goto loser;
    }

    PORT_Assert(entry->nickname == NULL);
    if (entry->nickname != NULL) {
        goto loser;
    }

    entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname);

    if (entry->nickname == NULL) {
        goto loser;
    }

    /* delete the subject entry */
    DeleteDBSubjectEntry(dbhandle, &cert->derSubject);

    /* write the new one */
    rv = WriteDBSubjectEntry(dbhandle, entry);
    if (rv != SECSuccess) {
        goto loser;
    }

    DestroyDBEntry((certDBEntry *)entry);
    return (SECSuccess);

loser:
    DestroyDBEntry((certDBEntry *)entry);
    return (SECFailure);
}

/*
 * create a new version entry
 */

static certDBEntryVersion *
NewDBVersionEntry(unsigned int flags)
{
    PLArenaPool *arena = NULL;
    certDBEntryVersion *entry;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = (certDBEntryVersion *)PORT_ArenaAlloc(arena,
                                                  sizeof(certDBEntryVersion));
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeVersion;
    entry->common.version = CERT_DB_FILE_VERSION;
    entry->common.flags = flags;

    return (entry);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * Read the version entry
 */

static certDBEntryVersion *
ReadDBVersionEntry(NSSLOWCERTCertDBHandle *handle)
{
    PLArenaPool *arena = NULL;
    PLArenaPool *tmparena = NULL;
    certDBEntryVersion *entry;
    SECItem dbkey;
    SECItem dbentry;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    entry = PORT_ArenaZNew(arena, certDBEntryVersion);
    if (entry == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    entry->common.arena = arena;
    entry->common.type = certDBEntryTypeVersion;

    /* now get the database key and format it */
    dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
    if (dbkey.data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY,
                SEC_DB_VERSION_KEY_LEN);

    rv = ReadDBEntry(handle, &entry->common, &dbkey, &dbentry, tmparena);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (entry);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (NULL);
}

/*
 * Encode a version entry into byte stream suitable for
 * the database
 */

static SECStatus
WriteDBVersionEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryVersion *entry)
{
    SECItem dbitem, dbkey;
    PLArenaPool *tmparena = NULL;
    SECStatus rv;

    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        goto loser;
    }

    /* allocate space for encoded database record, including space
     * for low level header
     */

    dbitem.len = SEC_DB_ENTRY_HEADER_LEN;

    dbitem.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbitem.len);
    if (dbitem.data == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* now get the database key and format it */
    dbkey.len = SEC_DB_VERSION_KEY_LEN + SEC_DB_KEY_HEADER_LEN;
    dbkey.data = (unsigned char *)PORT_ArenaAlloc(tmparena, dbkey.len);
    if (dbkey.data == NULL) {
        goto loser;
    }
    PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], SEC_DB_VERSION_KEY,
                SEC_DB_VERSION_KEY_LEN);

    /* now write it to the database */
    rv = WriteDBEntry(handle, &entry->common, &dbkey, &dbitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_FreeArena(tmparena, PR_FALSE);
    return (SECSuccess);

loser:
    if (tmparena) {
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    return (SECFailure);
}

/*
 * cert is no longer a perm cert, but will remain a temp cert
 */

static SECStatus
RemovePermSubjectNode(NSSLOWCERTCertificate *cert)
{
    certDBEntrySubject *entry;
    unsigned int i;
    SECStatus rv;

    entry = ReadDBSubjectEntry(cert->dbhandle, &cert->derSubject);
    if (entry == NULL) {
        return (SECFailure);
    }

    PORT_Assert(entry->ncerts);
    rv = SECFailure;

    if (entry->ncerts > 1) {
        for (i = 0; i < entry->ncerts; i++) {
            if (SECITEM_CompareItem(&entry->certKeys[i], &cert->certKey) ==
                SECEqual) {
                /* copy rest of list forward one entry */
                for (i = i + 1; i < entry->ncerts; i++) {
                    entry->certKeys[i - 1] = entry->certKeys[i];
                    entry->keyIDs[i - 1] = entry->keyIDs[i];
                }
                entry->ncerts--;
                DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
                rv = WriteDBSubjectEntry(cert->dbhandle, entry);
                break;
            }
        }
    } else {
        /* no entries left, delete the perm entry in the DB */
        if (entry->emailAddrs) {
            /* if the subject had an email record, then delete it too */
            for (i = 0; i < entry->nemailAddrs; i++) {
                DeleteDBSMimeEntry(cert->dbhandle, entry->emailAddrs[i]);
            }
        }
        if (entry->nickname) {
            DeleteDBNicknameEntry(cert->dbhandle, entry->nickname);
        }

        DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
    }
    DestroyDBEntry((certDBEntry *)entry);

    return (rv);
}

/*
 * add a cert to the perm subject list
 */

static SECStatus
AddPermSubjectNode(certDBEntrySubject *entry, NSSLOWCERTCertificate *cert,
                   char *nickname)
{
    SECItem *newCertKeys, *newKeyIDs;
    unsigned int i, new_i;
    SECStatus rv;
    unsigned int ncerts;

    PORT_Assert(entry);
    ncerts = entry->ncerts;

    if (nickname && entry->nickname) {
        /* nicknames must be the same */
        PORT_Assert(PORT_Strcmp(nickname, entry->nickname) == 0);
    }

    if ((entry->nickname == NULL) && (nickname != NULL)) {
        /* copy nickname into the entry */
        entry->nickname = PORT_ArenaStrdup(entry->common.arena, nickname);
        if (entry->nickname == NULL) {
            return (SECFailure);
        }
    }

    /* a DB entry already exists, so add this cert */
    newCertKeys = PORT_ArenaZNewArray(entry->common.arena, SECItem, ncerts + 1);
    newKeyIDs = PORT_ArenaZNewArray(entry->common.arena, SECItem, ncerts + 1);

    if ((newCertKeys == NULL) || (newKeyIDs == NULL)) {
        return (SECFailure);
    }

    /* Step 1: copy certs older than "cert" into new entry. */
    for (i = 0, new_i = 0; i < ncerts; i++) {
        NSSLOWCERTCertificate *cmpcert;
        PRBool isNewer;
        cmpcert = nsslowcert_FindCertByKey(cert->dbhandle,
                                           &entry->certKeys[i]);
        /* The entry has been corrupted, remove it from the list */
        if (!cmpcert) {
            continue;
        }

        isNewer = nsslowcert_IsNewer(cert, cmpcert);
        nsslowcert_DestroyCertificate(cmpcert);
        if (isNewer)
            break;
        /* copy this cert entry */
        newCertKeys[new_i] = entry->certKeys[i];
        newKeyIDs[new_i] = entry->keyIDs[i];
        new_i++;
    }

    /* Step 2: Add "cert" to the entry. */
    rv = SECITEM_CopyItem(entry->common.arena, &newCertKeys[new_i],
                          &cert->certKey);
    if (rv != SECSuccess) {
        return (SECFailure);
    }
    rv = SECITEM_CopyItem(entry->common.arena, &newKeyIDs[new_i],
                          &cert->subjectKeyID);
    if (rv != SECSuccess) {
        return (SECFailure);
    }
    new_i++;

    /* Step 3: copy remaining certs (if any) from old entry to new. */
    for (; i < ncerts; i++, new_i++) {
        newCertKeys[new_i] = entry->certKeys[i];
        newKeyIDs[new_i] = entry->keyIDs[i];
    }

    /* update certKeys and keyIDs */
    entry->certKeys = newCertKeys;
    entry->keyIDs = newKeyIDs;

    /* set new count value */
    entry->ncerts = new_i;

    DeleteDBSubjectEntry(cert->dbhandle, &cert->derSubject);
    rv = WriteDBSubjectEntry(cert->dbhandle, entry);
    return (rv);
}

SECStatus
nsslowcert_TraversePermCertsForSubject(NSSLOWCERTCertDBHandle *handle,
                                       SECItem *derSubject,
                                       NSSLOWCERTCertCallback cb, void *cbarg)
{
    certDBEntrySubject *entry;
    unsigned int i;
    NSSLOWCERTCertificate *cert;
    SECStatus rv = SECSuccess;

    entry = ReadDBSubjectEntry(handle, derSubject);

    if (entry == NULL) {
        return (SECFailure);
    }

    for (i = 0; i < entry->ncerts; i++) {
        cert = nsslowcert_FindCertByKey(handle, &entry->certKeys[i]);
        if (!cert) {
            continue;
        }
        rv = (*cb)(cert, cbarg);
        nsslowcert_DestroyCertificate(cert);
        if (rv == SECFailure) {
            break;
        }
    }

    DestroyDBEntry((certDBEntry *)entry);

    return (rv);
}

int
nsslowcert_NumPermCertsForSubject(NSSLOWCERTCertDBHandle *handle,
                                  SECItem *derSubject)
{
    certDBEntrySubject *entry;
    int ret;

    entry = ReadDBSubjectEntry(handle, derSubject);

    if (entry == NULL) {
        return (SECFailure);
    }

    ret = entry->ncerts;

    DestroyDBEntry((certDBEntry *)entry);

    return (ret);
}

SECStatus
nsslowcert_TraversePermCertsForNickname(NSSLOWCERTCertDBHandle *handle,
                                        char *nickname, NSSLOWCERTCertCallback cb, void *cbarg)
{
    certDBEntryNickname *nnentry = NULL;
    certDBEntrySMime *smentry = NULL;
    SECStatus rv;
    SECItem *derSubject = NULL;

    nnentry = ReadDBNicknameEntry(handle, nickname);
    if (nnentry) {
        derSubject = &nnentry->subjectName;
    } else {
        smentry = nsslowcert_ReadDBSMimeEntry(handle, nickname);
        if (smentry) {
            derSubject = &smentry->subjectName;
        }
    }

    if (derSubject) {
        rv = nsslowcert_TraversePermCertsForSubject(handle, derSubject,
                                                    cb, cbarg);
    } else {
        rv = SECFailure;
    }

    if (nnentry) {
        DestroyDBEntry((certDBEntry *)nnentry);
    }
    if (smentry) {
        DestroyDBEntry((certDBEntry *)smentry);
    }

    return (rv);
}

int
nsslowcert_NumPermCertsForNickname(NSSLOWCERTCertDBHandle *handle,
                                   char *nickname)
{
    certDBEntryNickname *entry;
    int ret;

    entry = ReadDBNicknameEntry(handle, nickname);

    if (entry) {
        ret = nsslowcert_NumPermCertsForSubject(handle, &entry->subjectName);
        DestroyDBEntry((certDBEntry *)entry);
    } else {
        ret = 0;
    }
    return (ret);
}

/*
 * add a nickname to a cert that doesn't have one
 */

static SECStatus
AddNicknameToPermCert(NSSLOWCERTCertDBHandle *dbhandle,
                      NSSLOWCERTCertificate *cert, char *nickname)
{
    certDBEntryCert *entry;
    int rv;

    entry = cert->dbEntry;
    PORT_Assert(entry != NULL);
    if (entry == NULL) {
        goto loser;
    }

    pkcs11_freeNickname(entry->nickname, entry->nicknameSpace);
    entry->nickname = NULL;
    entry->nickname = pkcs11_copyNickname(nickname, entry->nicknameSpace,
                                          sizeof(entry->nicknameSpace));

    rv = WriteDBCertEntry(dbhandle, entry);
    if (rv) {
        goto loser;
    }

    pkcs11_freeNickname(cert->nickname, cert->nicknameSpace);
    cert->nickname = NULL;
    cert->nickname = pkcs11_copyNickname(nickname, cert->nicknameSpace,
                                         sizeof(cert->nicknameSpace));

    return (SECSuccess);

loser:
    return (SECFailure);
}

/*
 * add a nickname to a cert that is already in the perm database, but doesn't
 * have one yet (it is probably an e-mail cert).
 */

SECStatus
nsslowcert_AddPermNickname(NSSLOWCERTCertDBHandle *dbhandle,
                           NSSLOWCERTCertificate *cert, char *nickname)
{
    SECStatus rv = SECFailure;
    certDBEntrySubject *entry = NULL;
    certDBEntryNickname *nicknameEntry = NULL;

    nsslowcert_LockDB(dbhandle);

    entry = ReadDBSubjectEntry(dbhandle, &cert->derSubject);
    if (entry == NULL)
        goto loser;

    if (entry->nickname == NULL) {

        /* no nickname for subject */
        rv = AddNicknameToSubject(dbhandle, cert, nickname);
        if (rv != SECSuccess) {
            goto loser;
        }
        rv = AddNicknameToPermCert(dbhandle, cert, nickname);
        if (rv != SECSuccess) {
            goto loser;
        }
        nicknameEntry = NewDBNicknameEntry(nickname, &cert->derSubject, 0);
        if (nicknameEntry == NULL) {
            goto loser;
        }

        rv = WriteDBNicknameEntry(dbhandle, nicknameEntry);
        if (rv != SECSuccess) {
            goto loser;
        }
    } else {
        /* subject already has a nickname */
        rv = AddNicknameToPermCert(dbhandle, cert, entry->nickname);
        if (rv != SECSuccess) {
            goto loser;
        }
        /* make sure nickname entry exists. If the database was corrupted,
         * we may have lost the nickname entry. Add it back now  */

        nicknameEntry = ReadDBNicknameEntry(dbhandle, entry->nickname);
        if (nicknameEntry == NULL) {
            nicknameEntry = NewDBNicknameEntry(entry->nickname,
                                               &cert->derSubject, 0);
            if (nicknameEntry == NULL) {
                goto loser;
            }

            rv = WriteDBNicknameEntry(dbhandle, nicknameEntry);
            if (rv != SECSuccess) {
                goto loser;
            }
        }
    }
    rv = SECSuccess;

loser:
    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }
    if (nicknameEntry) {
        DestroyDBEntry((certDBEntry *)nicknameEntry);
    }
    nsslowcert_UnlockDB(dbhandle);
    return (rv);
}

static certDBEntryCert *
AddCertToPermDB(NSSLOWCERTCertDBHandle *handle, NSSLOWCERTCertificate *cert,
                char *nickname, NSSLOWCERTCertTrust *trust)
{
    certDBEntryCert *certEntry = NULL;
    certDBEntryNickname *nicknameEntry = NULL;
    certDBEntrySubject *subjectEntry = NULL;
    int state = 0;
    SECStatus rv;
    PRBool donnentry = PR_FALSE;

    if (nickname) {
        donnentry = PR_TRUE;
    }

    subjectEntry = ReadDBSubjectEntry(handle, &cert->derSubject);

    if (subjectEntry && subjectEntry->nickname) {
        donnentry = PR_FALSE;
        nickname = subjectEntry->nickname;
    }

    certEntry = NewDBCertEntry(&cert->derCert, nickname, trust, 0);
    if (certEntry == NULL) {
        goto loser;
    }

    if (donnentry) {
        nicknameEntry = NewDBNicknameEntry(nickname, &cert->derSubject, 0);
        if (nicknameEntry == NULL) {
            goto loser;
        }
    }

    rv = WriteDBCertEntry(handle, certEntry);
    if (rv != SECSuccess) {
        goto loser;
    }
    state = 1;

    if (nicknameEntry) {
        rv = WriteDBNicknameEntry(handle, nicknameEntry);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    state = 2;

    /* "Change" handles if necessary */
    cert->dbhandle = handle;

    /* add to or create new subject entry */
    if (subjectEntry) {
        /* REWRITE BASED ON SUBJECT ENTRY */
        rv = AddPermSubjectNode(subjectEntry, cert, nickname);
        if (rv != SECSuccess) {
            goto loser;
        }
    } else {
        /* make a new subject entry - this case is only used when updating
         * an old version of the database.  This is OK because the oldnickname
         * db format didn't allow multiple certs with the same subject.
         */

        /* where does subjectKeyID and certKey come from? */
        subjectEntry = NewDBSubjectEntry(&cert->derSubject, &cert->certKey,
                                         &cert->subjectKeyID, nickname,
                                         NULL, 0);
        if (subjectEntry == NULL) {
            goto loser;
        }
        rv = WriteDBSubjectEntry(handle, subjectEntry);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    state = 3;

    if (nicknameEntry) {
        DestroyDBEntry((certDBEntry *)nicknameEntry);
    }

    if (subjectEntry) {
        DestroyDBEntry((certDBEntry *)subjectEntry);
    }

    return (certEntry);

loser:
    /* don't leave partial entry in the database */
    if (state > 0) {
        DeleteDBCertEntry(handle, &cert->certKey);
    }
    if ((state > 1) && donnentry) {
        DeleteDBNicknameEntry(handle, nickname);
    }
    if (certEntry) {
        DestroyDBEntry((certDBEntry *)certEntry);
    }
    if (nicknameEntry) {
        DestroyDBEntry((certDBEntry *)nicknameEntry);
    }
    if (subjectEntry) {
        DestroyDBEntry((certDBEntry *)subjectEntry);
    }

    return (NULL);
}

/* forward declaration */
static SECStatus
UpdateV7DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb);

/*
 * version 8 uses the same schema as version 7. The only differences are
 * 1) version 8 db uses the blob shim to store data entries > 32k.
 * 2) version 8 db sets the db block size to 32k.
 * both of these are dealt with by the handle.
 */


static SECStatus
UpdateV8DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb)
{
    return UpdateV7DB(handle, updatedb);
}

/*
 * we could just blindly sequence through reading key data pairs and writing
 * them back out, but some cert.db's have gotten quite large and may have some
 * subtle corruption problems, so instead we cycle through the certs and
 * CRL's and S/MIME profiles and rebuild our subject lists from those records.
 */

static SECStatus
UpdateV7DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb)
{
    DBT key, data;
    int ret;
    NSSLOWCERTCertificate *cert;
    PRBool isKRL = PR_FALSE;
    certDBEntryType entryType;
    SECItem dbEntry, dbKey;
    certDBEntryRevocation crlEntry;
    certDBEntryCert certEntry;
    certDBEntrySMime smimeEntry;
    SECStatus rv;

    ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST);

    if (ret) {
        return (SECFailure);
    }

    do {
        unsigned char *dataBuf = (unsigned char *)data.data;
        unsigned char *keyBuf = (unsigned char *)key.data;
        dbEntry.data = &dataBuf[SEC_DB_ENTRY_HEADER_LEN];
        dbEntry.len = data.size - SEC_DB_ENTRY_HEADER_LEN;
        entryType = (certDBEntryType)keyBuf[0];
        dbKey.data = &keyBuf[SEC_DB_KEY_HEADER_LEN];
        dbKey.len = key.size - SEC_DB_KEY_HEADER_LEN;
        if ((dbEntry.len <= 0) || (dbKey.len <= 0)) {
            continue;
        }

        switch (entryType) {
            /* these entries will get regenerated as we read the
             * rest of the data from the database */

            case certDBEntryTypeVersion:
            case certDBEntryTypeSubject:
            case certDBEntryTypeContentVersion:
            case certDBEntryTypeNickname:
            /* smime profiles need entries created after the certs have
             * been imported, loop over them in a second run */

            case certDBEntryTypeSMimeProfile:
                break;

            case certDBEntryTypeCert:
                /* decode Entry */
                certEntry.common.version = (unsigned int)dataBuf[0];
                certEntry.common.type = entryType;
                certEntry.common.flags = (unsigned int)dataBuf[2];
                rv = DecodeDBCertEntry(&certEntry, &dbEntry);
                if (rv != SECSuccess) {
                    break;
                }
                /* should we check for existing duplicates? */
                cert = nsslowcert_DecodeDERCertificate(&certEntry.derCert,
                                                       certEntry.nickname);
                if (cert) {
                    nsslowcert_UpdatePermCert(handle, cert, certEntry.nickname,
                                              &certEntry.trust);
                    nsslowcert_DestroyCertificate(cert);
                }
                /* free any data the decode may have allocated. */
                pkcs11_freeStaticData(certEntry.derCert.data,
                                      certEntry.derCertSpace);
                pkcs11_freeNickname(certEntry.nickname, certEntry.nicknameSpace);
                break;

            case certDBEntryTypeKeyRevocation:
                isKRL = PR_TRUE;
            /* fall through */
            case certDBEntryTypeRevocation:
                crlEntry.common.version = (unsigned int)dataBuf[0];
                crlEntry.common.type = entryType;
                crlEntry.common.flags = (unsigned int)dataBuf[2];
                crlEntry.common.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
                if (crlEntry.common.arena == NULL) {
                    break;
                }
                rv = DecodeDBCrlEntry(&crlEntry, &dbEntry);
                if (rv != SECSuccess) {
                    break;
                }
                nsslowcert_UpdateCrl(handle, &crlEntry.derCrl, &dbKey,
                                     crlEntry.url, isKRL);
                /* free data allocated by the decode */
                PORT_FreeArena(crlEntry.common.arena, PR_FALSE);
                crlEntry.common.arena = NULL;
                break;

            default:
                break;
        }
    } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0);

    /* now loop again updating just the SMimeProfile. */
    ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST);

    if (ret) {
        return (SECFailure);
    }

    do {
        unsigned char *dataBuf = (unsigned char *)data.data;
        unsigned char *keyBuf = (unsigned char *)key.data;
        dbEntry.data = &dataBuf[SEC_DB_ENTRY_HEADER_LEN];
        dbEntry.len = data.size - SEC_DB_ENTRY_HEADER_LEN;
        entryType = (certDBEntryType)keyBuf[0];
        if (entryType != certDBEntryTypeSMimeProfile) {
            continue;
        }
        dbKey.data = &keyBuf[SEC_DB_KEY_HEADER_LEN];
        dbKey.len = key.size - SEC_DB_KEY_HEADER_LEN;
        if ((dbEntry.len <= 0) || (dbKey.len <= 0)) {
            continue;
        }
        smimeEntry.common.version = (unsigned int)dataBuf[0];
        smimeEntry.common.type = entryType;
        smimeEntry.common.flags = (unsigned int)dataBuf[2];
        smimeEntry.common.arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
        /* decode entry */
        rv = DecodeDBSMimeEntry(&smimeEntry, &dbEntry, (char *)dbKey.data);
        if (rv == SECSuccess) {
            nsslowcert_UpdateSMimeProfile(handle, smimeEntry.emailAddr,
                                          &smimeEntry.subjectName, &smimeEntry.smimeOptions,
                                          &smimeEntry.optionsDate);
        }
        PORT_FreeArena(smimeEntry.common.arena, PR_FALSE);
        smimeEntry.common.arena = NULL;
    } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0);

    (*updatedb->close)(updatedb);

    /* a database update is a good time to go back and verify the integrity of
     * the keys and certs */

    handle->dbVerify = PR_TRUE;
    return (SECSuccess);
}

/*
 * NOTE - Version 6 DB did not go out to the real world in a release,
 * so we can remove this function in a later release.
 */

static SECStatus
UpdateV6DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb)
{
    int ret;
    DBT key, data;
    unsigned char *buf, *tmpbuf = NULL;
    certDBEntryType type;
    certDBEntryNickname *nnEntry = NULL;
    certDBEntrySubject *subjectEntry = NULL;
    certDBEntrySMime *emailEntry = NULL;
    char *nickname;
    char *emailAddr;

    /*
     * Sequence through the old database and copy all of the entries
     * to the new database.  Subject name entries will have the new
     * fields inserted into them (with zero length).
     */

    ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST);
    if (ret) {
        return (SECFailure);
    }

    do {
        buf = (unsigned char *)data.data;

        if (data.size >= 3) {
            if (buf[0] == 6) { /* version number */
                type = (certDBEntryType)buf[1];
                if (type == certDBEntryTypeSubject) {
                    /* expando subjecto entrieo */
                    tmpbuf = (unsigned char *)PORT_Alloc(data.size + 4);
                    if (tmpbuf) {
                        /* copy header stuff */
                        PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN + 2);
                        /* insert 4 more bytes of zero'd header */
                        PORT_Memset(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 2],
                                    0, 4);
                        /* copy rest of the data */
                        PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6],
                                    &buf[SEC_DB_ENTRY_HEADER_LEN + 2],
                                    data.size - (SEC_DB_ENTRY_HEADER_LEN + 2));

                        data.data = (void *)tmpbuf;
                        data.size += 4;
                        buf = tmpbuf;
                    }
                } else if (type == certDBEntryTypeCert) {
                    /* expando certo entrieo */
                    tmpbuf = (unsigned char *)PORT_Alloc(data.size + 3);
                    if (tmpbuf) {
                        /* copy header stuff */
                        PORT_Memcpy(tmpbuf, buf, SEC_DB_ENTRY_HEADER_LEN);

                        /* copy trust flage, setting msb's to 0 */
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN] = 0;
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 1] =
                            buf[SEC_DB_ENTRY_HEADER_LEN];
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 2] = 0;
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 3] =
                            buf[SEC_DB_ENTRY_HEADER_LEN + 1];
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 4] = 0;
                        tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 5] =
                            buf[SEC_DB_ENTRY_HEADER_LEN + 2];

                        /* copy rest of the data */
                        PORT_Memcpy(&tmpbuf[SEC_DB_ENTRY_HEADER_LEN + 6],
                                    &buf[SEC_DB_ENTRY_HEADER_LEN + 3],
                                    data.size - (SEC_DB_ENTRY_HEADER_LEN + 3));

                        data.data = (void *)tmpbuf;
                        data.size += 3;
                        buf = tmpbuf;
                    }
                }

                /* update the record version number */
                buf[0] = CERT_DB_FILE_VERSION;

                /* copy to the new database */
                ret = certdb_Put(handle->permCertDB, &key, &data, 0);
                if (tmpbuf) {
                    PORT_Free(tmpbuf);
                    tmpbuf = NULL;
                }
                if (ret) {
                    return SECFailure;
                }
            }
        }
    } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0);

    ret = certdb_Sync(handle->permCertDB, 0);
    if (ret) {
        return SECFailure;
    }

    ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST);
    if (ret) {
        return (SECFailure);
    }

    do {
        buf = (unsigned char *)data.data;

        if (data.size >= 3) {
            if (buf[0] == CERT_DB_FILE_VERSION) { /* version number */
                type = (certDBEntryType)buf[1];
                if (type == certDBEntryTypeNickname) {
                    nickname = &((char *)key.data)[1];

                    /* get the matching nickname entry in the new DB */
                    nnEntry = ReadDBNicknameEntry(handle, nickname);
                    if (nnEntry == NULL) {
                        goto endloop;
                    }

                    /* find the subject entry pointed to by nickname */
                    subjectEntry = ReadDBSubjectEntry(handle,
                                                      &nnEntry->subjectName);
                    if (subjectEntry == NULL) {
                        goto endloop;
                    }

                    subjectEntry->nickname =
                        (char *)PORT_ArenaAlloc(subjectEntry->common.arena,
                                                key.size - 1);
                    if (subjectEntry->nickname) {
                        PORT_Memcpy(subjectEntry->nickname, nickname,
                                    key.size - 1);
                        (void)WriteDBSubjectEntry(handle, subjectEntry);
                    }
                } else if (type == certDBEntryTypeSMimeProfile) {
                    emailAddr = &((char *)key.data)[1];

                    /* get the matching smime entry in the new DB */
                    emailEntry = nsslowcert_ReadDBSMimeEntry(handle, emailAddr);
                    if (emailEntry == NULL) {
                        goto endloop;
                    }

                    /* find the subject entry pointed to by nickname */
                    subjectEntry = ReadDBSubjectEntry(handle,
                                                      &emailEntry->subjectName);
                    if (subjectEntry == NULL) {
                        goto endloop;
                    }

                    subjectEntry->emailAddrs = (char **)
                        PORT_ArenaAlloc(subjectEntry->common.arena,
                                        sizeof(char *));
                    if (subjectEntry->emailAddrs) {
                        subjectEntry->emailAddrs[0] =
                            (char *)PORT_ArenaAlloc(subjectEntry->common.arena,
                                                    key.size - 1);
                        if (subjectEntry->emailAddrs[0]) {
                            PORT_Memcpy(subjectEntry->emailAddrs[0], emailAddr,
                                        key.size - 1);
                            subjectEntry->nemailAddrs = 1;
                            (void)WriteDBSubjectEntry(handle, subjectEntry);
                        }
                    }
                }

            endloop:
                if (subjectEntry) {
                    DestroyDBEntry((certDBEntry *)subjectEntry);
                    subjectEntry = NULL;
                }
                if (nnEntry) {
                    DestroyDBEntry((certDBEntry *)nnEntry);
                    nnEntry = NULL;
                }
                if (emailEntry) {
                    DestroyDBEntry((certDBEntry *)emailEntry);
                    emailEntry = NULL;
                }
            }
        }
    } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0);

    ret = certdb_Sync(handle->permCertDB, 0);
    if (ret) {
        return SECFailure;
    }

    (*updatedb->close)(updatedb);
    return (SECSuccess);
}

static SECStatus
updateV5Callback(NSSLOWCERTCertificate *cert, SECItem *k, void *pdata)
{
    NSSLOWCERTCertDBHandle *handle;
    certDBEntryCert *entry;
    NSSLOWCERTCertTrust *trust;

    handle = (NSSLOWCERTCertDBHandle *)pdata;
    trust = &cert->dbEntry->trust;

    /* SSL user certs can be used for email if they have an email addr */
    if (cert->emailAddr && (trust->sslFlags & CERTDB_USER) &&
        (trust->emailFlags == 0)) {
        trust->emailFlags = CERTDB_USER;
    }
    /* servers didn't set the user flags on the server cert.. */
    if (PORT_Strcmp(cert->dbEntry->nickname, "Server-Cert") == 0) {
        trust->sslFlags |= CERTDB_USER;
    }

    entry = AddCertToPermDB(handle, cert, cert->dbEntry->nickname,
                            &cert->dbEntry->trust);
    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }

    return (SECSuccess);
}

static SECStatus
UpdateV5DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb)
{
    NSSLOWCERTCertDBHandle updatehandle;

    updatehandle.permCertDB = updatedb;
    updatehandle.dbMon = PZ_NewMonitor(nssILockCertDB);
    updatehandle.dbVerify = 0;
    updatehandle.ref = 1; /* prevent premature close */

    (void)nsslowcert_TraversePermCerts(&updatehandle, updateV5Callback,
                                       (void *)handle);

    PZ_DestroyMonitor(updatehandle.dbMon);

    (*updatedb->close)(updatedb);
    return (SECSuccess);
}

static PRBool
isV4DB(DB *db)
{
    DBT key, data;
    int ret;

    key.data = "Version";
    key.size = 7;

    ret = (*db->get)(db, &key, &data, 0);
    if (ret) {
        return PR_FALSE;
    }

    if ((data.size == 1) && (*(unsigned char *)data.data <= 4)) {
        return PR_TRUE;
    }

    return PR_FALSE;
}

static SECStatus
UpdateV4DB(NSSLOWCERTCertDBHandle *handle, DB *updatedb)
{
    DBT key, data;
    certDBEntryCert *entry, *entry2;
    int ret;
    NSSLOWCERTCertificate *cert;

    ret = (*updatedb->seq)(updatedb, &key, &data, R_FIRST);

    if (ret) {
        return (SECFailure);
    }

    do {
        if (data.size != 1) { /* skip version number */

            /* decode the old DB entry */
            entry = (certDBEntryCert *)
                DecodeV4DBCertEntry((unsigned char *)data.data, data.size);

            if (entry) {
                cert = nsslowcert_DecodeDERCertificate(&entry->derCert,
                                                       entry->nickname);

                if (cert != NULL) {
                    /* add to new database */
                    entry2 = AddCertToPermDB(handle, cert, entry->nickname,
                                             &entry->trust);

                    nsslowcert_DestroyCertificate(cert);
                    if (entry2) {
                        DestroyDBEntry((certDBEntry *)entry2);
                    }
                }
                DestroyDBEntry((certDBEntry *)entry);
            }
        }
    } while ((*updatedb->seq)(updatedb, &key, &data, R_NEXT) == 0);

    (*updatedb->close)(updatedb);
    return (SECSuccess);
}

/*
 * return true if a database key conflict exists
 */

PRBool
nsslowcert_CertDBKeyConflict(SECItem *derCert, NSSLOWCERTCertDBHandle *handle)
{
    SECStatus rv;
    DBT tmpdata;
    DBT namekey;
    int ret;
    SECItem keyitem;
    PLArenaPool *arena = NULL;
    SECItem derKey;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    /* get the db key of the cert */
    rv = nsslowcert_KeyFromDERCert(arena, derCert, &derKey);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = EncodeDBCertKey(&derKey, arena, &keyitem);
    if (rv != SECSuccess) {
        goto loser;
    }

    namekey.data = keyitem.data;
    namekey.size = keyitem.len;

    ret = certdb_Get(handle->permCertDB, &namekey, &tmpdata, 0);
    if (ret == 0) {
        goto loser;
    }

    PORT_FreeArena(arena, PR_FALSE);

    return (PR_FALSE);
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return (PR_TRUE);
}

/*
 * return true if a nickname conflict exists
 * NOTE: caller must have already made sure that this exact cert
 * doesn't exist in the DB
 */

static PRBool
nsslowcert_CertNicknameConflict(char *nickname, SECItem *derSubject,
                                NSSLOWCERTCertDBHandle *handle)
{
    PRBool rv;
    certDBEntryNickname *entry;

    if (nickname == NULL) {
        return (PR_FALSE);
    }

    entry = ReadDBNicknameEntry(handle, nickname);

    if (entry == NULL) {
        /* no entry for this nickname, so no conflict */
        return (PR_FALSE);
    }

    rv = PR_TRUE;
    if (SECITEM_CompareItem(derSubject, &entry->subjectName) == SECEqual) {
        /* if subject names are the same, then no conflict */
        rv = PR_FALSE;
    }

    DestroyDBEntry((certDBEntry *)entry);
    return (rv);
}

#ifdef DBM_USING_NSPR
#define NO_RDONLY PR_RDONLY
#define NO_RDWR PR_RDWR
#define NO_CREATE (PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE)
#else
#define NO_RDONLY O_RDONLY
#define NO_RDWR O_RDWR
#define NO_CREATE (O_RDWR | O_CREAT | O_TRUNC)
#endif

/*
 * open an old database that needs to be updated
 */

static DB *
nsslowcert_openolddb(NSSLOWCERTDBNameFunc namecb, void *cbarg, int version)
{
    char *tmpname;
    DB *updatedb = NULL;

    tmpname = (*namecb)(cbarg, version); /* get v6 db name */
    if (tmpname) {
        updatedb = dbopen(tmpname, NO_RDONLY, 0600, DB_HASH, 0);
        PORT_Free(tmpname);
    }
    return updatedb;
}

static SECStatus
openNewCertDB(const char *appName, const char *prefix, const char *certdbname,
              NSSLOWCERTCertDBHandle *handle, NSSLOWCERTDBNameFunc namecb, void *cbarg)
{
    SECStatus rv;
    certDBEntryVersion *versionEntry = NULL;
    DB *updatedb = NULL;
    int status = RDB_FAIL;

    if (appName) {
        handle->permCertDB = rdbopen(appName, prefix, "cert", NO_CREATE, &status);
    } else {
        handle->permCertDB = dbsopen(certdbname, NO_CREATE, 0600, DB_HASH, 0);
    }

    /* if create fails then we lose */
    if (handle->permCertDB == 0) {
        return status == RDB_RETRY ? SECWouldBlock : SECFailure;
    }

    /* Verify version number; */
    versionEntry = NewDBVersionEntry(0);
    if (versionEntry == NULL) {
        rv = SECFailure;
        goto loser;
    }

    rv = WriteDBVersionEntry(handle, versionEntry);

    DestroyDBEntry((certDBEntry *)versionEntry);

    if (rv != SECSuccess) {
        goto loser;
    }

    /* rv must already be Success here because of previous if statement */
    /* try to upgrade old db here */
    if (appName &&
        (updatedb = dbsopen(certdbname, NO_RDONLY, 0600, DB_HASH, 0)) != NULL) {
        rv = UpdateV8DB(handle, updatedb);
    } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 7)) != NULL) {
        rv = UpdateV7DB(handle, updatedb);
    } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 6)) != NULL) {
        rv = UpdateV6DB(handle, updatedb);
    } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 5)) != NULL) {
        rv = UpdateV5DB(handle, updatedb);
    } else if ((updatedb = nsslowcert_openolddb(namecb, cbarg, 4)) != NULL) {
        /* NES has v5 format db's with v4 db names! */
        if (isV4DB(updatedb)) {
            rv = UpdateV4DB(handle, updatedb);
        } else {
            rv = UpdateV5DB(handle, updatedb);
        }
    }

loser:
    db_InitComplete(handle->permCertDB);
    return rv;
}

static int
nsslowcert_GetVersionNumber(NSSLOWCERTCertDBHandle *handle)
{
    certDBEntryVersion *versionEntry = NULL;
    int version = 0;

    versionEntry = ReadDBVersionEntry(handle);
    if (versionEntry == NULL) {
        return 0;
    }
    version = versionEntry->common.version;
    DestroyDBEntry((certDBEntry *)versionEntry);
    return version;
}

/*
 * Open the certificate database and index databases.  Create them if
 * they are not there or bad.
 */

static SECStatus
nsslowcert_OpenPermCertDB(NSSLOWCERTCertDBHandle *handle, PRBool readOnly,
                          const char *appName, const char *prefix,
                          NSSLOWCERTDBNameFunc namecb, void *cbarg)
{
    SECStatus rv;
    int openflags;
    char *certdbname;
    int version = 0;

    certdbname = (*namecb)(cbarg, CERT_DB_FILE_VERSION);
    if (certdbname == NULL) {
        return (SECFailure);
    }

    openflags = readOnly ? NO_RDONLY : NO_RDWR;

    /*
     * first open the permanent file based database.
     */

    if (appName) {
        handle->permCertDB = rdbopen(appName, prefix, "cert", openflags, NULL);
    } else {
        handle->permCertDB = dbsopen(certdbname, openflags, 0600, DB_HASH, 0);
    }

    /* check for correct version number */
    if (handle->permCertDB) {
        version = nsslowcert_GetVersionNumber(handle);
        if ((version != CERT_DB_FILE_VERSION) &&
            !(appName && version == CERT_DB_V7_FILE_VERSION)) {
            goto loser;
        }
    } else if (readOnly) {
        /* don't create if readonly */
        /* Try openning a version 7 database */
        handle->permCertDB = nsslowcert_openolddb(namecb, cbarg, 7);
        if (!handle->permCertDB) {
            goto loser;
        }
        if (nsslowcert_GetVersionNumber(handle) != 7) {
            goto loser;
        }
    } else {
        /* if first open fails, try to create a new DB */
        rv = openNewCertDB(appName, prefix, certdbname, handle, namecb, cbarg);
        if (rv == SECWouldBlock) {
            /* only the rdb version can fail with wouldblock */
            handle->permCertDB =
                rdbopen(appName, prefix, "cert", openflags, NULL);

            /* check for correct version number */
            if (!handle->permCertDB) {
                goto loser;
            }
            version = nsslowcert_GetVersionNumber(handle);
            if ((version != CERT_DB_FILE_VERSION) &&
                !(appName && version == CERT_DB_V7_FILE_VERSION)) {
                goto loser;
            }
        } else if (rv != SECSuccess) {
            goto loser;
        }
    }

    PORT_Free(certdbname);

    return (SECSuccess);

loser:

    PORT_SetError(SEC_ERROR_BAD_DATABASE);

    if (handle->permCertDB) {
        certdb_Close(handle->permCertDB);
        handle->permCertDB = 0;
    }

    PORT_Free(certdbname);

    return (SECFailure);
}

/*
 * delete all DB records associated with a particular certificate
 */

static SECStatus
DeletePermCert(NSSLOWCERTCertificate *cert)
{
    SECStatus rv;
    SECStatus ret;

    ret = SECSuccess;

    rv = DeleteDBCertEntry(cert->dbhandle, &cert->certKey);
    if (rv != SECSuccess) {
        ret = SECFailure;
    }

    rv = RemovePermSubjectNode(cert);

    return (ret);
}

/*
 * Delete a certificate from the permanent database.
 */

SECStatus
nsslowcert_DeletePermCertificate(NSSLOWCERTCertificate *cert)
{
    SECStatus rv;

    nsslowcert_LockDB(cert->dbhandle);

    /* delete the records from the permanent database */
    rv = DeletePermCert(cert);

    /* get rid of dbcert and stuff pointing to it */
    DestroyDBEntry((certDBEntry *)cert->dbEntry);
    cert->dbEntry = NULL;
    cert->trust = NULL;

    nsslowcert_UnlockDB(cert->dbhandle);
    return (rv);
}

/*
 * Traverse all of the entries in the database of a particular type
 * call the given function for each one.
 */

SECStatus
nsslowcert_TraverseDBEntries(NSSLOWCERTCertDBHandle *handle,
                             certDBEntryType type,
                             SECStatus (*callback)(SECItem *data, SECItem *key,
                                                   certDBEntryType type, void *pdata),
                             void *udata)
{
    DBT data;
    DBT key;
    SECStatus rv = SECSuccess;
    int ret;
    SECItem dataitem;
    SECItem keyitem;
    unsigned char *buf;
    unsigned char *keybuf;

    ret = certdb_Seq(handle->permCertDB, &key, &data, R_FIRST);
    if (ret) {
        return (SECFailure);
    }
    /* here, ret is zero and rv is SECSuccess.
     * Below here, ret is a count of successful calls to the callback function.
     */

    do {
        buf = (unsigned char *)data.data;

        if (buf[1] == (unsigned char)type) {
            dataitem.len = data.size;
            dataitem.data = buf;
            dataitem.type = siBuffer;
            keyitem.len = key.size - SEC_DB_KEY_HEADER_LEN;
            keybuf = (unsigned char *)key.data;
            keyitem.data = &keybuf[SEC_DB_KEY_HEADER_LEN];
            keyitem.type = siBuffer;
            /* type should equal keybuf[0].  */

            rv = (*callback)(&dataitem, &keyitem, type, udata);
            if (rv == SECSuccess) {
                ++ret;
            }
        }
    } while (certdb_Seq(handle->permCertDB, &key, &data, R_NEXT) == 0);
    /* If any callbacks succeeded, or no calls to callbacks were made,
     * then report success.  Otherwise, report failure.
     */

    return (ret ? SECSuccess : rv);
}
/*
 * Decode a certificate and enter it into the temporary certificate database.
 * Deal with nicknames correctly
 *
 * This is the private entry point.
 */

static NSSLOWCERTCertificate *
DecodeACert(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry)
{
    NSSLOWCERTCertificate *cert = NULL;

    cert = nsslowcert_DecodeDERCertificate(&entry->derCert, entry->nickname);

    if (cert == NULL) {
        goto loser;
    }

    cert->dbhandle = handle;
    cert->dbEntry = entry;
    cert->trust = &entry->trust;

    return (cert);

loser:
    return (0);
}

static NSSLOWCERTTrust *
CreateTrust(void)
{
    NSSLOWCERTTrust *trust = NULL;

    nsslowcert_LockFreeList();
    trust = trustListHead;
    if (trust) {
        trustListCount--;
        trustListHead = trust->next;
        trust->next = NULL;
    }
    PORT_Assert(trustListCount >= 0);
    nsslowcert_UnlockFreeList();
    if (trust) {
        return trust;
    }

    return PORT_ZNew(NSSLOWCERTTrust);
}

static void
DestroyTrustFreeList(void)
{
    NSSLOWCERTTrust *trust;

    nsslowcert_LockFreeList();
    while (NULL != (trust = trustListHead)) {
        trustListCount--;
        trustListHead = trust->next;
        PORT_Free(trust);
    }
    PORT_Assert(!trustListCount);
    trustListCount = 0;
    nsslowcert_UnlockFreeList();
}

static NSSLOWCERTTrust *
DecodeTrustEntry(NSSLOWCERTCertDBHandle *handle, certDBEntryCert *entry,
                 const SECItem *dbKey)
{
    NSSLOWCERTTrust *trust = CreateTrust();
    if (trust == NULL) {
        return trust;
    }
    trust->dbhandle = handle;
    trust->dbEntry = entry;
    trust->dbKey.data = pkcs11_copyStaticData(dbKey->data, dbKey->len,
                                              trust->dbKeySpace, sizeof(trust->dbKeySpace));
    if (!trust->dbKey.data) {
        PORT_Free(trust);
        return NULL;
    }
    trust->dbKey.len = dbKey->len;

    trust->trust = &entry->trust;
    trust->derCert = &entry->derCert;

    return (trust);
}

typedef struct {
    PermCertCallback certfunc;
    NSSLOWCERTCertDBHandle *handle;
    void *data;
} PermCertCallbackState;

/*
 * traversal callback to decode certs and call callers callback
 */

static SECStatus
certcallback(SECItem *dbdata, SECItem *dbkey, certDBEntryType type, void *data)
{
    PermCertCallbackState *mystate;
    SECStatus rv;
    certDBEntryCert *entry;
    SECItem entryitem;
    NSSLOWCERTCertificate *cert;
    PLArenaPool *arena = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    entry = (certDBEntryCert *)PORT_ArenaAlloc(arena, sizeof(certDBEntryCert));
    if (!entry) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    mystate = (PermCertCallbackState *)data;
    entry->common.version = (unsigned int)dbdata->data[0];
    entry->common.type = (certDBEntryType)dbdata->data[1];
    entry->common.flags = (unsigned int)dbdata->data[2];
    entry->common.arena = arena;

    entryitem.len = dbdata->len - SEC_DB_ENTRY_HEADER_LEN;
    entryitem.data = &dbdata->data[SEC_DB_ENTRY_HEADER_LEN];

    rv = DecodeDBCertEntry(entry, &entryitem);
    if (rv != SECSuccess) {
        goto loser;
    }
    entry->derCert.type = siBuffer;

    /* note: Entry is 'inheritted'.  */
    cert = DecodeACert(mystate->handle, entry);

    rv = (*mystate->certfunc)(cert, dbkey, mystate->data);

    /* arena stored in entry destroyed by nsslowcert_DestroyCertificate */
    nsslowcert_DestroyCertificateNoLocking(cert);

    return (rv);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return (SECFailure);
}

/*
 * Traverse all of the certificates in the permanent database and
 * call the given function for each one; expect the caller to have lock.
 */

static SECStatus
TraversePermCertsNoLocking(NSSLOWCERTCertDBHandle *handle,
                           SECStatus (*certfunc)(NSSLOWCERTCertificate *cert,
                                                 SECItem *k,
                                                 void *pdata),
                           void *udata)
{
    SECStatus rv;
    PermCertCallbackState mystate;

    mystate.certfunc = certfunc;
    mystate.handle = handle;
    mystate.data = udata;
    rv = nsslowcert_TraverseDBEntries(handle, certDBEntryTypeCert, certcallback,
                                      (void *)&mystate);

    return (rv);
}

/*
 * Traverse all of the certificates in the permanent database and
 * call the given function for each one.
 */

SECStatus
nsslowcert_TraversePermCerts(NSSLOWCERTCertDBHandle *handle,
                             SECStatus (*certfunc)(NSSLOWCERTCertificate *cert, SECItem *k,
                                                   void *pdata),
                             void *udata)
{
    SECStatus rv;

    nsslowcert_LockDB(handle);
    rv = TraversePermCertsNoLocking(handle, certfunc, udata);
    nsslowcert_UnlockDB(handle);

    return (rv);
}

/*
 * Close the database
 */

void
nsslowcert_ClosePermCertDB(NSSLOWCERTCertDBHandle *handle)
{
    if (handle) {
        if (handle->permCertDB) {
            certdb_Close(handle->permCertDB);
            handle->permCertDB = NULL;
        }
        if (handle->dbMon) {
            PZ_DestroyMonitor(handle->dbMon);
            handle->dbMon = NULL;
        }
        PORT_Free(handle);
    }
    return;
}

/*
 * Get the trust attributes from a certificate
 */

SECStatus
nsslowcert_GetCertTrust(NSSLOWCERTCertificate *cert, NSSLOWCERTCertTrust *trust)
{
    SECStatus rv;

    nsslowcert_LockCertTrust(cert);

    if (cert->trust == NULL) {
        rv = SECFailure;
    } else {
        *trust = *cert->trust;
        rv = SECSuccess;
    }

    nsslowcert_UnlockCertTrust(cert);
    return (rv);
}

/*
 * Change the trust attributes of a certificate and make them permanent
 * in the database.
 */

SECStatus
nsslowcert_ChangeCertTrust(NSSLOWCERTCertDBHandle *handle,
                           NSSLOWCERTCertificate *cert, NSSLOWCERTCertTrust *trust)
{
    certDBEntryCert *entry;
    int rv;
    SECStatus ret;

    nsslowcert_LockDB(handle);
    nsslowcert_LockCertTrust(cert);
    /* only set the trust on permanent certs */
    if (cert->trust == NULL) {
        ret = SECFailure;
        goto done;
    }

    *cert->trust = *trust;
    if (cert->dbEntry == NULL) {
        ret = SECSuccess; /* not in permanent database */
        goto done;
    }

    entry = cert->dbEntry;
    entry->trust = *trust;

    rv = WriteDBCertEntry(handle, entry);
    if (rv) {
        ret = SECFailure;
        goto done;
    }

    ret = SECSuccess;

done:
    nsslowcert_UnlockCertTrust(cert);
    nsslowcert_UnlockDB(handle);
    return (ret);
}

static SECStatus
nsslowcert_UpdatePermCert(NSSLOWCERTCertDBHandle *dbhandle,
                          NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust)
{
    char *oldnn;
    certDBEntryCert *entry;
    PRBool conflict;
    SECStatus ret;

    PORT_Assert(!cert->dbEntry);

    /* don't add a conflicting nickname */
    conflict = nsslowcert_CertNicknameConflict(nickname, &cert->derSubject,
                                               dbhandle);
    if (conflict) {
        ret = SECFailure;
        goto done;
    }

    /* save old nickname so that we can delete it */
    oldnn = cert->nickname;

    entry = AddCertToPermDB(dbhandle, cert, nickname, trust);

    if (entry == NULL) {
        ret = SECFailure;
        goto done;
    }

    pkcs11_freeNickname(oldnn, cert->nicknameSpace);

    cert->nickname = (entry->nickname) ? pkcs11_copyNickname(entry->nickname,
                                                             cert->nicknameSpace, sizeof(cert->nicknameSpace))
                                       : NULL;
    cert->trust = &entry->trust;
    cert->dbEntry = entry;

    ret = SECSuccess;
done:
    return (ret);
}

SECStatus
nsslowcert_AddPermCert(NSSLOWCERTCertDBHandle *dbhandle,
                       NSSLOWCERTCertificate *cert, char *nickname, NSSLOWCERTCertTrust *trust)
{
    SECStatus ret;

    nsslowcert_LockDB(dbhandle);

    ret = nsslowcert_UpdatePermCert(dbhandle, cert, nickname, trust);

    nsslowcert_UnlockDB(dbhandle);
    return (ret);
}

/*
 * Open the certificate database and index databases.  Create them if
 * they are not there or bad.
 */

SECStatus
nsslowcert_OpenCertDB(NSSLOWCERTCertDBHandle *handle, PRBool readOnly,
                      const char *appName, const char *prefix,
                      NSSLOWCERTDBNameFunc namecb, void *cbarg, PRBool openVolatile)
{
    int rv;

    certdb_InitDBLock(handle);

    handle->dbMon = PZ_NewMonitor(nssILockCertDB);
    PORT_Assert(handle->dbMon != NULL);
    handle->dbVerify = PR_FALSE;

    rv = nsslowcert_OpenPermCertDB(handle, readOnly, appName, prefix,
                                   namecb, cbarg);
    if (rv) {
        goto loser;
    }

    return (SECSuccess);

loser:
    if (handle->dbMon) {
        PZ_DestroyMonitor(handle->dbMon);
        handle->dbMon = NULL;
    }
    PORT_SetError(SEC_ERROR_BAD_DATABASE);
    return (SECFailure);
}

PRBool
nsslowcert_needDBVerify(NSSLOWCERTCertDBHandle *handle)
{
    if (!handle)
        return PR_FALSE;
    return handle->dbVerify;
}

void
nsslowcert_setDBVerify(NSSLOWCERTCertDBHandle *handle, PRBool value)
{
    handle->dbVerify = value;
}

/*
 * Lookup a certificate in the databases.
 */

static NSSLOWCERTCertificate *
FindCertByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey, PRBool lockdb)
{
    NSSLOWCERTCertificate *cert = NULL;
    certDBEntryCert *entry;
    PRBool locked = PR_FALSE;

    if (lockdb) {
        locked = PR_TRUE;
        nsslowcert_LockDB(handle);
    }

    /* find in perm database */
    entry = ReadDBCertEntry(handle, certKey);

    if (entry == NULL) {
        goto loser;
    }

    /* inherit entry */
    cert = DecodeACert(handle, entry);

loser:
    if (cert == NULL) {
        if (entry) {
            DestroyDBEntry((certDBEntry *)entry);
        }
    }

    if (locked) {
        nsslowcert_UnlockDB(handle);
    }

    return (cert);
}

/*
 * Lookup a certificate in the databases.
 */

static NSSLOWCERTTrust *
FindTrustByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey, PRBool lockdb)
{
    NSSLOWCERTTrust *trust = NULL;
    certDBEntryCert *entry;
    PRBool locked = PR_FALSE;

    if (lockdb) {
        locked = PR_TRUE;
        nsslowcert_LockDB(handle);
    }

    /* find in perm database */
    entry = ReadDBCertEntry(handle, certKey);

    if (entry == NULL) {
        goto loser;
    }

    if (!nsslowcert_hasTrust(&entry->trust)) {
        goto loser;
    }

    /* inherit entry */
    trust = DecodeTrustEntry(handle, entry, certKey);

loser:
    if (trust == NULL) {
        if (entry) {
            DestroyDBEntry((certDBEntry *)entry);
        }
    }

    if (locked) {
        nsslowcert_UnlockDB(handle);
    }

    return (trust);
}

/*
 * Lookup a certificate in the databases without locking
 */

NSSLOWCERTCertificate *
nsslowcert_FindCertByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey)
{
    return (FindCertByKey(handle, certKey, PR_FALSE));
}

/*
 * Lookup a trust object in the databases without locking
 */

NSSLOWCERTTrust *
nsslowcert_FindTrustByKey(NSSLOWCERTCertDBHandle *handle, const SECItem *certKey)
{
    return (FindTrustByKey(handle, certKey, PR_FALSE));
}

/*
 * Generate a key from an issuerAndSerialNumber, and find the
 * associated cert in the database.
 */

NSSLOWCERTCertificate *
nsslowcert_FindCertByIssuerAndSN(NSSLOWCERTCertDBHandle *handle, NSSLOWCERTIssuerAndSN *issuerAndSN)
{
    SECItem certKey;
    SECItem *sn = &issuerAndSN->serialNumber;
    SECItem *issuer = &issuerAndSN->derIssuer;
    NSSLOWCERTCertificate *cert;
    int data_len = sn->len;
    int index = 0;

    /* automatically detect DER encoded serial numbers and remove the der
     * encoding since the database expects unencoded data.
     * if it's DER encoded, there must be at least 3 bytes, tag, len, data */

    if ((sn->len >= 3) && (sn->data[0] == 0x2)) {
        /* remove the der encoding of the serial number before generating the
         * key.. */

        int data_left = sn->len - 2;
        data_len = sn->data[1];
        index = 2;

        /* extended length ? (not very likely for a serial number) */
        if (data_len & 0x80) {
            int len_count = data_len & 0x7f;

            data_len = 0;
            data_left -= len_count;
            if (data_left > 0) {
                while (len_count--) {
                    data_len = (data_len << 8) | sn->data[index++];
                }
            }
        }
        /* XXX leaving any leading zeros on the serial number for backwards
         * compatibility
         */

        /* not a valid der, must be just an unlucky serial number value */
        if (data_len != data_left) {
            data_len = sn->len;
            index = 0;
        }
    }

    certKey.type = 0;
    certKey.data = (unsigned char *)PORT_Alloc(sn->len + issuer->len);
    certKey.len = data_len + issuer->len;

    if (certKey.data == NULL) {
        return (0);
    }

    /* first try the serial number as hand-decoded above*/
    /* copy the serialNumber */
    PORT_Memcpy(certKey.data, &sn->data[index], data_len);

    /* copy the issuer */
    PORT_Memcpy(&certKey.data[data_len], issuer->data, issuer->len);

    cert = nsslowcert_FindCertByKey(handle, &certKey);
    if (cert) {
        PORT_Free(certKey.data);
        return (cert);
    }

    /* didn't find it, try by der encoded serial number */
    /* copy the serialNumber */
    PORT_Memcpy(certKey.data, sn->data, sn->len);

    /* copy the issuer */
    PORT_Memcpy(&certKey.data[sn->len], issuer->data, issuer->len);
    certKey.len = sn->len + issuer->len;

    cert = nsslowcert_FindCertByKey(handle, &certKey);

    PORT_Free(certKey.data);

    return (cert);
}

/*
 * Generate a key from an issuerAndSerialNumber, and find the
 * associated cert in the database.
 */

NSSLOWCERTTrust *
nsslowcert_FindTrustByIssuerAndSN(NSSLOWCERTCertDBHandle *handle,
                                  NSSLOWCERTIssuerAndSN *issuerAndSN)
{
    SECItem certKey;
    SECItem *sn = &issuerAndSN->serialNumber;
    SECItem *issuer = &issuerAndSN->derIssuer;
    NSSLOWCERTTrust *trust;
    unsigned char keyBuf[512];
    int data_len = sn->len;
    int index = 0;
    int len;

    /* automatically detect DER encoded serial numbers and remove the der
     * encoding since the database expects unencoded data.
     * if it's DER encoded, there must be at least 3 bytes, tag, len, data */

    if ((sn->len >= 3) && (sn->data[0] == 0x2)) {
        /* remove the der encoding of the serial number before generating the
         * key.. */

        int data_left = sn->len - 2;
        data_len = sn->data[1];
        index = 2;

        /* extended length ? (not very likely for a serial number) */
        if (data_len & 0x80) {
            int len_count = data_len & 0x7f;

            data_len = 0;
            data_left -= len_count;
            if (data_left > 0) {
                while (len_count--) {
                    data_len = (data_len << 8) | sn->data[index++];
                }
            }
        }
        /* XXX leaving any leading zeros on the serial number for backwards
         * compatibility
         */

        /* not a valid der, must be just an unlucky serial number value */
        if (data_len != data_left) {
            data_len = sn->len;
            index = 0;
        }
    }

    certKey.type = 0;
    certKey.len = data_len + issuer->len;
    len = sn->len + issuer->len;
    if (len > sizeof(keyBuf)) {
        certKey.data = (unsigned char *)PORT_Alloc(len);
    } else {
        certKey.data = keyBuf;
    }

    if (certKey.data == NULL) {
        return (0);
    }

    /* first try the serial number as hand-decoded above*/
    /* copy the serialNumber */
    PORT_Memcpy(certKey.data, &sn->data[index], data_len);

    /* copy the issuer */
    PORT_Memcpy(&certKey.data[data_len], issuer->data, issuer->len);

    trust = nsslowcert_FindTrustByKey(handle, &certKey);
    if (trust) {
        pkcs11_freeStaticData(certKey.data, keyBuf);
        return (trust);
    }

    if (index == 0) {
        pkcs11_freeStaticData(certKey.data, keyBuf);
        return NULL;
    }

    /* didn't find it, try by der encoded serial number */
    /* copy the serialNumber */
    PORT_Memcpy(certKey.data, sn->data, sn->len);

    /* copy the issuer */
    PORT_Memcpy(&certKey.data[sn->len], issuer->data, issuer->len);
    certKey.len = sn->len + issuer->len;

    trust = nsslowcert_FindTrustByKey(handle, &certKey);

    pkcs11_freeStaticData(certKey.data, keyBuf);

    return (trust);
}

/*
 * look for the given DER certificate in the database
 */

NSSLOWCERTCertificate *
nsslowcert_FindCertByDERCert(NSSLOWCERTCertDBHandle *handle, SECItem *derCert)
{
    PLArenaPool *arena;
    SECItem certKey;
    SECStatus rv;
    NSSLOWCERTCertificate *cert = NULL;

    /* create a scratch arena */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        return (NULL);
    }

    /* extract the database key from the cert */
    rv = nsslowcert_KeyFromDERCert(arena, derCert, &certKey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* find the certificate */
    cert = nsslowcert_FindCertByKey(handle, &certKey);

loser:
    PORT_FreeArena(arena, PR_FALSE);
    return (cert);
}

static void
DestroyCertificate(NSSLOWCERTCertificate *cert, PRBool lockdb)
{
    int refCount;
    NSSLOWCERTCertDBHandle *handle;

    if (cert) {

        handle = cert->dbhandle;

        /*
         * handle may be NULL, for example if the cert was created with
         * nsslowcert_DecodeDERCertificate.
         */

        if (lockdb && handle) {
            nsslowcert_LockDB(handle);
        }

        nsslowcert_LockCertRefCount(cert);
        PORT_Assert(cert->referenceCount > 0);
        refCount = --cert->referenceCount;
        nsslowcert_UnlockCertRefCount(cert);

        if (refCount == 0) {
            certDBEntryCert *entry = cert->dbEntry;

            if (entry) {
                DestroyDBEntry((certDBEntry *)entry);
            }

            pkcs11_freeNickname(cert->nickname, cert->nicknameSpace);
            pkcs11_freeNickname(cert->emailAddr, cert->emailAddrSpace);
            pkcs11_freeStaticData(cert->certKey.data, cert->certKeySpace);
            cert->certKey.data = NULL;
            cert->nickname = NULL;

            /* zero cert before freeing. Any stale references to this cert
             * after this point will probably cause an exception.  */

            PORT_Memset(cert, 0, sizeof *cert);

            /* use reflock to protect the free list */
            nsslowcert_LockFreeList();
            if (certListCount > MAX_CERT_LIST_COUNT) {
                PORT_Free(cert);
            } else {
                certListCount++;
                cert->next = certListHead;
                certListHead = cert;
            }
            nsslowcert_UnlockFreeList();
            cert = NULL;
        }
        if (lockdb && handle) {
            nsslowcert_UnlockDB(handle);
        }
    }

    return;
}

NSSLOWCERTCertificate *
nsslowcert_CreateCert(void)
{
    NSSLOWCERTCertificate *cert;
    nsslowcert_LockFreeList();
    cert = certListHead;
    if (cert) {
        certListHead = cert->next;
        certListCount--;
    }
    PORT_Assert(certListCount >= 0);
    nsslowcert_UnlockFreeList();
    if (cert) {
        return cert;
    }
    return PORT_ZNew(NSSLOWCERTCertificate);
}

static void
DestroyCertFreeList(void)
{
    NSSLOWCERTCertificate *cert;

    nsslowcert_LockFreeList();
    while (NULL != (cert = certListHead)) {
        certListCount--;
        certListHead = cert->next;
        PORT_Free(cert);
    }
    PORT_Assert(!certListCount);
    certListCount = 0;
    nsslowcert_UnlockFreeList();
}

void
nsslowcert_DestroyTrust(NSSLOWCERTTrust *trust)
{
    certDBEntryCert *entry = trust->dbEntry;

    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }
    pkcs11_freeStaticData(trust->dbKey.data, trust->dbKeySpace);
    PORT_Memset(trust, 0, sizeof(*trust));

    nsslowcert_LockFreeList();
    if (trustListCount > MAX_TRUST_LIST_COUNT) {
        PORT_Free(trust);
    } else {
        trustListCount++;
        trust->next = trustListHead;
        trustListHead = trust;
    }
    nsslowcert_UnlockFreeList();

    return;
}

void
nsslowcert_DestroyCertificate(NSSLOWCERTCertificate *cert)
{
    DestroyCertificate(cert, PR_TRUE);
    return;
}

static void
nsslowcert_DestroyCertificateNoLocking(NSSLOWCERTCertificate *cert)
{
    DestroyCertificate(cert, PR_FALSE);
    return;
}

/*
 * Lookup a CRL in the databases. We mirror the same fast caching data base
 *  caching stuff used by certificates....?
 */

certDBEntryRevocation *
nsslowcert_FindCrlByKey(NSSLOWCERTCertDBHandle *handle,
                        SECItem *crlKey, PRBool isKRL)
{
    SECItem keyitem;
    SECStatus rv;
    PLArenaPool *arena = NULL;
    certDBEntryRevocation *entry = NULL;
    certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation
                                    : certDBEntryTypeRevocation;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    rv = EncodeDBGenericKey(crlKey, arena, &keyitem, crlType);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* find in perm database */
    entry = ReadDBCrlEntry(handle, crlKey, crlType);

    if (entry == NULL) {
        goto loser;
    }

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return entry;
}

/*
 * replace the existing URL in the data base with a new one
 */

static SECStatus
nsslowcert_UpdateCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl,
                     SECItem *crlKey, char *url, PRBool isKRL)
{
    SECStatus rv = SECFailure;
    certDBEntryRevocation *entry = NULL;
    certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation
                                    : certDBEntryTypeRevocation;
    DeleteDBCrlEntry(handle, crlKey, crlType);

    /* Write the new entry into the data base */
    entry = NewDBCrlEntry(derCrl, url, crlType, 0);
    if (entry == NULL)
        goto done;

    rv = WriteDBCrlEntry(handle, entry, crlKey);
    if (rv != SECSuccess)
        goto done;

done:
    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }
    return rv;
}

SECStatus
nsslowcert_AddCrl(NSSLOWCERTCertDBHandle *handle, SECItem *derCrl,
                  SECItem *crlKey, char *url, PRBool isKRL)
{
    SECStatus rv;

    rv = nsslowcert_UpdateCrl(handle, derCrl, crlKey, url, isKRL);

    return rv;
}

SECStatus
nsslowcert_DeletePermCRL(NSSLOWCERTCertDBHandle *handle, const SECItem *derName,
                         PRBool isKRL)
{
    SECStatus rv;
    certDBEntryType crlType = isKRL ? certDBEntryTypeKeyRevocation
                                    : certDBEntryTypeRevocation;

    rv = DeleteDBCrlEntry(handle, derName, crlType);
    if (rv != SECSuccess)
        goto done;

done:
    return rv;
}

PRBool
nsslowcert_hasTrust(NSSLOWCERTCertTrust *trust)
{
    if (trust == NULL) {
        return PR_FALSE;
    }
    /* if we only have CERTDB__USER and CERTDB_TRUSTED_UNKNOWN bits, then
     * we don't have a trust record. */

    return !(((trust->sslFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0) &&
             ((trust->emailFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0) &&
             ((trust->objectSigningFlags & ~(CERTDB_USER | CERTDB_TRUSTED_UNKNOWN)) == 0));
}

/*
 * This function has the logic that decides if another person's cert and
 * email profile from an S/MIME message should be saved.  It can deal with
 * the case when there is no profile.
 */

static SECStatus
nsslowcert_UpdateSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle,
                              char *emailAddr, SECItem *derSubject, SECItem *emailProfile,
                              SECItem *profileTime)
{
    certDBEntrySMime *entry = NULL;
    SECStatus rv = SECFailure;
    ;

    /* find our existing entry */
    entry = nsslowcert_ReadDBSMimeEntry(dbhandle, emailAddr);

    if (entry) {
        /* keep our old db entry consistant for old applications. */
        if (!SECITEM_ItemsAreEqual(derSubject, &entry->subjectName)) {
            nsslowcert_UpdateSubjectEmailAddr(dbhandle, &entry->subjectName,
                                              emailAddr, nsslowcert_remove);
        }
        DestroyDBEntry((certDBEntry *)entry);
        entry = NULL;
    }

    /* now save the entry */
    entry = NewDBSMimeEntry(emailAddr, derSubject, emailProfile,
                            profileTime, 0);
    if (entry == NULL) {
        rv = SECFailure;
        goto loser;
    }

    nsslowcert_LockDB(dbhandle);

    rv = DeleteDBSMimeEntry(dbhandle, emailAddr);
    /* if delete fails, try to write new entry anyway... */

    /* link subject entry back here */
    rv = nsslowcert_UpdateSubjectEmailAddr(dbhandle, derSubject, emailAddr,
                                           nsslowcert_add);
    if (rv != SECSuccess) {
        nsslowcert_UnlockDB(dbhandle);
        goto loser;
    }

    rv = WriteDBSMimeEntry(dbhandle, entry);
    if (rv != SECSuccess) {
        nsslowcert_UnlockDB(dbhandle);
        goto loser;
    }

    nsslowcert_UnlockDB(dbhandle);

    rv = SECSuccess;

loser:
    if (entry) {
        DestroyDBEntry((certDBEntry *)entry);
    }
    return (rv);
}

SECStatus
nsslowcert_SaveSMimeProfile(NSSLOWCERTCertDBHandle *dbhandle, char *emailAddr,
                            SECItem *derSubject, SECItem *emailProfile, SECItem *profileTime)
{
    SECStatus rv = SECFailure;
    ;

    rv = nsslowcert_UpdateSMimeProfile(dbhandle, emailAddr,
                                       derSubject, emailProfile, profileTime);

    return (rv);
}

void
nsslowcert_DestroyFreeLists(void)
{
    if (freeListLock == NULL) {
        return;
    }
    DestroyCertEntryFreeList();
    DestroyTrustFreeList();
    DestroyCertFreeList();
    SKIP_AFTER_FORK(PZ_DestroyLock(freeListLock));
    freeListLock = NULL;
}

void
nsslowcert_DestroyGlobalLocks(void)
{
    if (dbLock) {
        SKIP_AFTER_FORK(PZ_DestroyLock(dbLock));
        dbLock = NULL;
    }
    if (certRefCountLock) {
        SKIP_AFTER_FORK(PZ_DestroyLock(certRefCountLock));
        certRefCountLock = NULL;
    }
    if (certTrustLock) {
        SKIP_AFTER_FORK(PZ_DestroyLock(certTrustLock));
        certTrustLock = NULL;
    }
}

certDBEntry *
nsslowcert_DecodeAnyDBEntry(SECItem *dbData, const SECItem *dbKey,
                            certDBEntryType entryType, void *pdata)
{
    PLArenaPool *arena = NULL;
    certDBEntry *entry;
    SECStatus rv;
    SECItem dbEntry;

    if ((dbData->len < SEC_DB_ENTRY_HEADER_LEN) || (dbKey->len == 0)) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        goto loser;
    }
    dbEntry.data = &dbData->data[SEC_DB_ENTRY_HEADER_LEN];
    dbEntry.len = dbData->len - SEC_DB_ENTRY_HEADER_LEN;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }
    entry = PORT_ArenaZNew(arena, certDBEntry);
    if (!entry)
        goto loser;

    entry->common.version = (unsigned int)dbData->data[0];
    entry->common.flags = (unsigned int)dbData->data[2];
    entry->common.type = entryType;
    entry->common.arena = arena;

    switch (entryType) {
        case certDBEntryTypeContentVersion: /* This type appears to be unused */
        case certDBEntryTypeVersion:        /* This type has only the common hdr */
            rv = SECSuccess;
            break;

        case certDBEntryTypeSubject:
            rv = DecodeDBSubjectEntry(&entry->subject, &dbEntry, dbKey);
            break;

        case certDBEntryTypeNickname:
            rv = DecodeDBNicknameEntry(&entry->nickname, &dbEntry,
                                       (char *)dbKey->data);
            break;

        /* smime profiles need entries created after the certs have
         * been imported, loop over them in a second run */

        case certDBEntryTypeSMimeProfile:
            rv = DecodeDBSMimeEntry(&entry->smime, &dbEntry, (char *)dbKey->data);
            break;

        case certDBEntryTypeCert:
            rv = DecodeDBCertEntry(&entry->cert, &dbEntry);
            break;

        case certDBEntryTypeKeyRevocation:
        case certDBEntryTypeRevocation:
            rv = DecodeDBCrlEntry(&entry->revocation, &dbEntry);
            break;

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

    if (rv == SECSuccess)
        return entry;

loser:
    if (arena)
        PORT_FreeArena(arena, PR_FALSE);
    return NULL;
}

Messung V0.5 in Prozent
C=98 H=74 G=86

¤ Dauer der Verarbeitung: 0.63 Sekunden  (vorverarbeitet am  2026-05-09) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.