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