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

/*
** dbck.c
**
** utility for fixing corrupt cert databases
**
*/
#include <stdio.h>
#include <string.h>

#include "secutil.h"
#include "cdbhdl.h"
#include "certdb.h"
#include "cert.h"
#include "nspr.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"
#include "pcert.h"
#include "nss.h"

static char *progName;

/* placeholders for pointer error types */
static void *WrongEntry;
static void *NoNickname;
static void *NoSMime;

typedef enum {
    /* 0*/ NoSubjectForCert = 0,
    /* 1*/ SubjectHasNoKeyForCert,
    /* 2*/ NoNicknameOrSMimeForSubject,
    /* 3*/ WrongNicknameForSubject,
    /* 4*/ NoNicknameEntry,
    /* 5*/ WrongSMimeForSubject,
    /* 6*/ NoSMimeEntry,
    /* 7*/ NoSubjectForNickname,
    /* 8*/ NoSubjectForSMime,
    /* 9*/ NicknameAndSMimeEntries,
    NUM_ERROR_TYPES
} dbErrorType;

static char *dbErrorString[NUM_ERROR_TYPES] = {
    /* 0*/ "<CERT ENTRY>\nDid not find a subject entry for this certificate.",
    /* 1*/ "<SUBJECT ENTRY>\nSubject has certKey which is not in db.",
    /* 2*/ "<SUBJECT ENTRY>\nSubject does not have a nickname or email address.",
    /* 3*/ "<SUBJECT ENTRY>\nUsing this subject's nickname, found a nickname entry for a different subject.",
    /* 4*/ "<SUBJECT ENTRY>\nDid not find a nickname entry for this subject.",
    /* 5*/ "<SUBJECT ENTRY>\nUsing this subject's email, found an S/MIME entry for a different subject.",
    /* 6*/ "<SUBJECT ENTRY>\nDid not find an S/MIME entry for this subject.",
    /* 7*/ "<NICKNAME ENTRY>\nDid not find a subject entry for this nickname.",
    /* 8*/ "<S/MIME ENTRY>\nDid not find a subject entry for this S/MIME profile.",
};

static char *errResult[NUM_ERROR_TYPES] = {
    "Certificate entries that had no subject entry.",
    "Subject entries with no corresponding Certificate entries.",
    "Subject entries that had no nickname or S/MIME entries.",
    "Redundant nicknames (subjects with the same nickname).",
    "Subject entries that had no nickname entry.",
    "Redundant email addresses (subjects with the same email address).",
    "Subject entries that had no S/MIME entry.",
    "Nickname entries that had no subject entry.",
    "S/MIME entries that had no subject entry.",
    "Subject entries with BOTH nickname and S/MIME entries."
};

enum {
    GOBOTH = 0,
    GORIGHT,
    GOLEFT
};

typedef struct
{
    PRBool verbose;
    PRBool dograph;
    PRFileDesc *out;
    PRFileDesc *graphfile;
    int dbErrors[NUM_ERROR_TYPES];
} dbDebugInfo;

struct certDBEntryListNodeStr {
    PRCList link;
    certDBEntry entry;
    void *appData;
};
typedef struct certDBEntryListNodeStr certDBEntryListNode;

/*
 * A list node for a cert db entry.  The index is a unique identifier
 * to use for creating generic maps of a db.  This struct handles
 * the cert, nickname, and smime db entry types, as all three have a
 * single handle to a subject entry.
 * This structure is pointed to by certDBEntryListNode->appData.
 */
typedef struct
{
    PLArenaPool *arena;
    int index;
    certDBEntryListNode *pSubject;
} certDBEntryMap;

/*
 * Subject entry is special case, it has bidirectional handles.  One
 * subject entry can point to several certs (using the same DN), and
 * a nickname and/or smime entry.
 * This structure is pointed to by certDBEntryListNode->appData.
 */
typedef struct
{
    PLArenaPool *arena;
    int index;
    int numCerts;
    certDBEntryListNode **pCerts;
    certDBEntryListNode *pNickname;
    certDBEntryListNode *pSMime;
} certDBSubjectEntryMap;

/*
 * A map of a certdb.
 */
typedef struct
{
    int numCerts;
    int numSubjects;
    int numNicknames;
    int numSMime;
    int numRevocation;
    certDBEntryListNode certs;      /* pointer to head of cert list */
    certDBEntryListNode subjects;   /* pointer to head of subject list */
    certDBEntryListNode nicknames;  /* pointer to head of nickname list */
    certDBEntryListNode smime;      /* pointer to head of smime list */
    certDBEntryListNode revocation; /* pointer to head of revocation list */
} certDBArray;

/* Cast list to the base element, a certDBEntryListNode. */
#define LISTNODE_CAST(node) \
    ((certDBEntryListNode *)(node))

static void
Usage(char *progName)
{
#define FPS fprintf(stderr,
    FPS "Type %s -H for more detailed descriptions\n", progName);
    FPS "Usage:  %s -D [-d certdir] [-m] [-v [-f dumpfile]]\n",
    progName);
#ifdef DORECOVER
    FPS "        %s -R -o newdbname [-d certdir] [-aprsx] [-v [-f dumpfile]]\n",
    progName);
#endif
    exit(-1);
}

static void
LongUsage(char *progName)
{
    FPS "%-15s Display this help message.\n",
    "-H");
    FPS "%-15s Dump analysis.  No changes will be made to the database.\n",
    "-D");
    FPS "%-15s Cert database directory (default is ~/.netscape)\n",
    "   -d certdir");
    FPS "%-15s Put database graph in ./mailfile (default is stdout).\n",
    "   -m");
    FPS "%-15s Verbose mode.  Dumps the entire contents of your cert8.db.\n",
    "   -v");
    FPS "%-15s File to dump verbose output into. (default is stdout)\n",
    "   -f dumpfile");
#ifdef DORECOVER
    FPS "%-15s Repair the database.  The program will look for broken\n",
    "-R");
    FPS "%-15s dependencies between subject entries and certificates,\n",
        "");
    FPS "%-15s between nickname entries and subjects, and between SMIME\n",
        "");
    FPS "%-15s profiles and subjects.  Any duplicate entries will be\n",
        "");
    FPS "%-15s removed, any missing entries will be created.\n",
        "");
    FPS "%-15s File to store new database in (default is new_cert8.db)\n",
    "   -o newdbname");
    FPS "%-15s Cert database directory (default is ~/.netscape)\n",
    "   -d certdir");
    FPS "%-15s Prompt before removing any certificates.\n",
        "   -p");
    FPS "%-15s Keep all possible certificates.  Only remove certificates\n",
    "   -a");
    FPS "%-15s which prevent creation of a consistent database.  Thus any\n",
    "");
    FPS "%-15s expired or redundant entries will be kept.\n",
    "");
    FPS "%-15s Keep redundant nickname/email entries.  It is possible\n",
    "   -r");
    FPS "%-15s only one such entry will be usable.\n",
    "");
    FPS "%-15s Don't require an S/MIME profile in order to keep an S/MIME\n",
    "   -s");
    FPS "%-15s cert.  An empty profile will be created.\n",
    "");
    FPS "%-15s Keep expired certificates.\n",
    "   -x");
    FPS "%-15s Verbose mode - report all activity while recovering db.\n",
    "   -v");
    FPS "%-15s File to dump verbose output into.\n",
    "   -f dumpfile");
    FPS "\n");
#endif
    exit(-1);
#undef FPS
}

/*******************************************************************
 *
 *  Functions for dbck.
 *
 ******************************************************************/

void
printHexString(PRFileDesc *out, SECItem *hexval)
{
    unsigned int i;
    for (i = 0; i < hexval->len; i++) {
        if (i != hexval->len - 1) {
            PR_fprintf(out, "%02x:", hexval->data[i]);
        } else {
            PR_fprintf(out, "%02x", hexval->data[i]);
        }
    }
    PR_fprintf(out, "\n");
}

SECStatus
dumpCertificate(CERTCertificate *cert, int num, PRFileDesc *outfile)
{
    int userCert = 0;
    CERTCertTrust *trust = cert->trust;
    userCert = (SEC_GET_TRUST_FLAGS(trust, trustSSL) & CERTDB_USER) ||
               (SEC_GET_TRUST_FLAGS(trust, trustEmail) & CERTDB_USER) ||
               (SEC_GET_TRUST_FLAGS(trust, trustObjectSigning) & CERTDB_USER);
    if (num >= 0) {
        PR_fprintf(outfile, "Certificate: %3d\n", num);
    } else {
        PR_fprintf(outfile, "Certificate:\n");
    }
    PR_fprintf(outfile, "----------------\n");
    if (userCert)
        PR_fprintf(outfile, "(User Cert)\n");
    PR_fprintf(outfile, "## SUBJECT:  %s\n", cert->subjectName);
    PR_fprintf(outfile, "## ISSUER:  %s\n", cert->issuerName);
    PR_fprintf(outfile, "## SERIAL NUMBER:  ");
    printHexString(outfile, &cert->serialNumber);
    { /*  XXX should be separate function.  */
        PRTime timeBefore, timeAfter;
        PRExplodedTime beforePrintable, afterPrintable;
        char *beforestr, *afterstr;
        DER_DecodeTimeChoice(&timeBefore, &cert->validity.notBefore);
        DER_DecodeTimeChoice(&timeAfter, &cert->validity.notAfter);
        PR_ExplodeTime(timeBefore, PR_GMTParameters, &beforePrintable);
        PR_ExplodeTime(timeAfter, PR_GMTParameters, &afterPrintable);
        beforestr = PORT_Alloc(100);
        afterstr = PORT_Alloc(100);
        PR_FormatTime(beforestr, 100, "%a %b %d %H:%M:%S %Y", &beforePrintable);
        PR_FormatTime(afterstr, 100, "%a %b %d %H:%M:%S %Y", &afterPrintable);
        PR_fprintf(outfile, "## VALIDITY:  %s to %s\n", beforestr, afterstr);
    }
    PR_fprintf(outfile, "\n");
    return SECSuccess;
}

SECStatus
dumpCertEntry(certDBEntryCert *entry, int num, PRFileDesc *outfile)
{
#if 0
    NSSLOWCERTCertificate *cert;
    /* should we check for existing duplicates? */
    cert = nsslowcert_DecodeDERCertificate(&entry->cert.derCert,
                        entry->cert.nickname);
#else
    CERTCertificate *cert;
    cert = CERT_DecodeDERCertificate(&entry->derCert, PR_FALSE, NULL);
#endif
    if (!cert) {
        fprintf(stderr, "Failed to decode certificate.\n");
        return SECFailure;
    }
    cert->trust = (CERTCertTrust *)&entry->trust;
    dumpCertificate(cert, num, outfile);
    CERT_DestroyCertificate(cert);
    return SECSuccess;
}

SECStatus
dumpSubjectEntry(certDBEntrySubject *entry, int num, PRFileDesc *outfile)
{
    char *subjectName = CERT_DerNameToAscii(&entry->derSubject);

    PR_fprintf(outfile, "Subject: %3d\n", num);
    PR_fprintf(outfile, "------------\n");
    PR_fprintf(outfile, "## %s\n", subjectName);
    if (entry->nickname)
        PR_fprintf(outfile, "## Subject nickname:  %s\n", entry->nickname);
    if (entry->emailAddrs) {
        unsigned int n;
        for (n = 0; n < entry->nemailAddrs && entry->emailAddrs[n]; ++n) {
            char *emailAddr = entry->emailAddrs[n];
            if (emailAddr[0]) {
                PR_fprintf(outfile, "## Subject email address:  %s\n",
                           emailAddr);
            }
        }
    }
    PR_fprintf(outfile, "## This subject has %d cert(s).\n", entry->ncerts);
    PR_fprintf(outfile, "\n");
    PORT_Free(subjectName);
    return SECSuccess;
}

SECStatus
dumpNicknameEntry(certDBEntryNickname *entry, int num, PRFileDesc *outfile)
{
    PR_fprintf(outfile, "Nickname: %3d\n", num);
    PR_fprintf(outfile, "-------------\n");
    PR_fprintf(outfile, "##  \"%s\"\n\n", entry->nickname);
    return SECSuccess;
}

SECStatus
dumpSMimeEntry(certDBEntrySMime *entry, int num, PRFileDesc *outfile)
{
    PR_fprintf(outfile, "S/MIME Profile: %3d\n", num);
    PR_fprintf(outfile, "-------------------\n");
    PR_fprintf(outfile, "##  \"%s\"\n", entry->emailAddr);
#ifdef OLDWAY
    PR_fprintf(outfile, "##  OPTIONS:  ");
    printHexString(outfile, &entry->smimeOptions);
    PR_fprintf(outfile, "##  TIMESTAMP:  ");
    printHexString(outfile, &entry->optionsDate);
#else
    SECU_PrintAny(stdout, &entry->smimeOptions, "##  OPTIONS  ", 0);
    fflush(stdout);
    if (entry->optionsDate.len && entry->optionsDate.data)
        PR_fprintf(outfile, "##  TIMESTAMP: %.*s\n",
                   entry->optionsDate.len, entry->optionsDate.data);
#endif
    PR_fprintf(outfile, "\n");
    return SECSuccess;
}

SECStatus
mapCertEntries(certDBArray *dbArray)
{
    certDBEntryCert *certEntry;
    certDBEntrySubject *subjectEntry;
    certDBEntryListNode *certNode, *subjNode;
    certDBSubjectEntryMap *smap;
    certDBEntryMap *map;
    PLArenaPool *tmparena;
    SECItem derSubject;
    SECItem certKey;
    PRCList *cElem, *sElem;

    /* Arena for decoded entries */
    tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (tmparena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    /* Iterate over cert entries and map them to subject entries.
     * NOTE: mapSubjectEntries must be called first to alloc memory
     * for array of subject->cert map.
     */
    for (cElem = PR_LIST_HEAD(&dbArray->certs.link);
         cElem != &dbArray->certs.link; cElem = PR_NEXT_LINK(cElem)) {
        certNode = LISTNODE_CAST(cElem);
        certEntry = (certDBEntryCert *)&certNode->entry;
        map = (certDBEntryMap *)certNode->appData;
        CERT_NameFromDERCert(&certEntry->derCert, &derSubject);
        CERT_KeyFromDERCert(tmparena, &certEntry->derCert, &certKey);
        /*  Loop over found subjects for cert's DN.  */
        for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
             sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
            subjNode = LISTNODE_CAST(sElem);
            subjectEntry = (certDBEntrySubject *)&subjNode->entry;
            if (SECITEM_ItemsAreEqual(&derSubject, &subjectEntry->derSubject)) {
                unsigned int i;
                /*  Found matching subject name, create link.  */
                map->pSubject = subjNode;
                /*  Make sure subject entry has cert's key.  */
                for (i = 0; i < subjectEntry->ncerts; i++) {
                    if (SECITEM_ItemsAreEqual(&certKey,
                                              &subjectEntry->certKeys[i])) {
                        /*  Found matching cert key.  */
                        smap = (certDBSubjectEntryMap *)subjNode->appData;
                        smap->pCerts[i] = certNode;
                        break;
                    }
                }
            }
        }
    }
    PORT_FreeArena(tmparena, PR_FALSE);
    return SECSuccess;
}

SECStatus
mapSubjectEntries(certDBArray *dbArray)
{
    certDBEntrySubject *subjectEntry;
    certDBEntryListNode *subjNode;
    certDBSubjectEntryMap *subjMap;
    PRCList *sElem;

    for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
         sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
        /* Iterate over subject entries and map subjects to nickname
         * and smime entries.  The cert<->subject map will be handled
         * by a subsequent call to mapCertEntries.
         */
        subjNode = LISTNODE_CAST(sElem);
        subjectEntry = (certDBEntrySubject *)&subjNode->entry;
        subjMap = (certDBSubjectEntryMap *)subjNode->appData;
        /* need to alloc memory here for array of matching certs. */
        subjMap->pCerts = PORT_ArenaAlloc(subjMap->arena,
                                          subjectEntry->ncerts * sizeof(int));
        subjMap->numCerts = subjectEntry->ncerts;
        subjMap->pNickname = NoNickname;
        subjMap->pSMime = NoSMime;

        if (subjectEntry->nickname) {
            /* Subject should have a nickname entry, so create a link. */
            PRCList *nElem;
            for (nElem = PR_LIST_HEAD(&dbArray->nicknames.link);
                 nElem != &dbArray->nicknames.link;
                 nElem = PR_NEXT_LINK(nElem)) {
                certDBEntryListNode *nickNode;
                certDBEntryNickname *nicknameEntry;
                /*  Look for subject's nickname in nickname entries.  */
                nickNode = LISTNODE_CAST(nElem);
                nicknameEntry = (certDBEntryNickname *)&nickNode->entry;
                if (PL_strcmp(subjectEntry->nickname,
                              nicknameEntry->nickname) == 0) {
                    /*  Found a nickname entry for subject's nickname.  */
                    if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject,
                                              &nicknameEntry->subjectName)) {
                        certDBEntryMap *nickMap;
                        nickMap = (certDBEntryMap *)nickNode->appData;
                        /*  Nickname and subject match.  */
                        subjMap->pNickname = nickNode;
                        nickMap->pSubject = subjNode;
                    } else if (subjMap->pNickname == NoNickname) {
                        /*  Nickname entry found is for diff. subject.  */
                        subjMap->pNickname = WrongEntry;
                    }
                }
            }
        }
        if (subjectEntry->emailAddrs) {
            unsigned int n;
            for (n = 0; n < subjectEntry->nemailAddrs &&
                        subjectEntry->emailAddrs[n];
                 ++n) {
                char *emailAddr = subjectEntry->emailAddrs[n];
                if (emailAddr[0]) {
                    PRCList *mElem;
                    /* Subject should have an smime entry, so create a link. */
                    for (mElem = PR_LIST_HEAD(&dbArray->smime.link);
                         mElem != &dbArray->smime.link;
                         mElem = PR_NEXT_LINK(mElem)) {
                        certDBEntryListNode *smimeNode;
                        certDBEntrySMime *smimeEntry;
                        /*  Look for subject's email in S/MIME entries.  */
                        smimeNode = LISTNODE_CAST(mElem);
                        smimeEntry = (certDBEntrySMime *)&smimeNode->entry;
                        if (PL_strcmp(emailAddr,
                                      smimeEntry->emailAddr) == 0) {
                            /*  Found a S/MIME entry for subject's email.  */
                            if (SECITEM_ItemsAreEqual(
                                    &subjectEntry->derSubject,
                                    &smimeEntry->subjectName)) {
                                certDBEntryMap *smimeMap;
                                /*  S/MIME entry and subject match.  */
                                subjMap->pSMime = smimeNode;
                                smimeMap = (certDBEntryMap *)smimeNode->appData;
                                smimeMap->pSubject = subjNode;
                            } else if (subjMap->pSMime == NoSMime) {
                                /*  S/MIME entry found is for diff. subject.  */
                                subjMap->pSMime = WrongEntry;
                            }
                        }
                    } /* end for */
                }     /* endif (emailAddr[0]) */
            }         /* end for */
        }             /* endif (subjectEntry->emailAddrs) */
    }
    return SECSuccess;
}

void
printnode(dbDebugInfo *info, const char *str, int num)
{
    if (!info->dograph)
        return;
    if (num < 0) {
        PR_fprintf(info->graphfile, str);
    } else {
        PR_fprintf(info->graphfile, str, num);
    }
}

PRBool
map_handle_is_ok(dbDebugInfo *info, void *mapPtr, int indent)
{
    if (mapPtr == NULL) {
        if (indent > 0)
            printnode(info, "                ", -1);
        if (indent >= 0)
            printnode(info, "******************* ", -1);
        return PR_FALSE;
    } else if (mapPtr == WrongEntry) {
        if (indent > 0)
            printnode(info, "                  ", -1);
        if (indent >= 0)
            printnode(info, "??????????????????? ", -1);
        return PR_FALSE;
    } else {
        return PR_TRUE;
    }
}

/* these call each other */
void print_smime_graph(dbDebugInfo *info, certDBEntryMap *smimeMap,
                       int direction);
void print_nickname_graph(dbDebugInfo *info, certDBEntryMap *nickMap,
                          int direction);
void print_subject_graph(dbDebugInfo *info, certDBSubjectEntryMap *subjMap,
                         int direction, int optindex, int opttype);
void print_cert_graph(dbDebugInfo *info, certDBEntryMap *certMap,
                      int direction);

/* Given an smime entry, print its unique identifier.  If GOLEFT is
 * specified, print the cert<-subject<-smime map, else just print
 * the smime entry.
 */
void
print_smime_graph(dbDebugInfo *info, certDBEntryMap *smimeMap, int direction)
{
    certDBSubjectEntryMap *subjMap;
    certDBEntryListNode *subjNode;
    if (direction == GOLEFT) {
        /* Need to output subject and cert first, see print_subject_graph */
        subjNode = smimeMap->pSubject;
        if (map_handle_is_ok(info, (void *)subjNode, 1)) {
            subjMap = (certDBSubjectEntryMap *)subjNode->appData;
            print_subject_graph(info, subjMap, GOLEFT,
                                smimeMap->index, certDBEntryTypeSMimeProfile);
        } else {
            printnode(info, "<---- S/MIME   %5d   ", smimeMap->index);
            info->dbErrors[NoSubjectForSMime]++;
        }
    } else {
        printnode(info, "S/MIME   %5d   ", smimeMap->index);
    }
}

/* Given a nickname entry, print its unique identifier.  If GOLEFT is
 * specified, print the cert<-subject<-nickname map, else just print
 * the nickname entry.
 */
void
print_nickname_graph(dbDebugInfo *info, certDBEntryMap *nickMap, int direction)
{
    certDBSubjectEntryMap *subjMap;
    certDBEntryListNode *subjNode;
    if (direction == GOLEFT) {
        /* Need to output subject and cert first, see print_subject_graph */
        subjNode = nickMap->pSubject;
        if (map_handle_is_ok(info, (void *)subjNode, 1)) {
            subjMap = (certDBSubjectEntryMap *)subjNode->appData;
            print_subject_graph(info, subjMap, GOLEFT,
                                nickMap->index, certDBEntryTypeNickname);
        } else {
            printnode(info, "<---- Nickname %5d   ", nickMap->index);
            info->dbErrors[NoSubjectForNickname]++;
        }
    } else {
        printnode(info, "Nickname %5d   ", nickMap->index);
    }
}

/* Given a subject entry, if going right print the graph of the nickname|smime
 * that it maps to (by its unique identifier); and if going left
 * print the list of certs that it points to.
 */
void
print_subject_graph(dbDebugInfo *info, certDBSubjectEntryMap *subjMap,
                    int direction, int optindex, int opttype)
{
    certDBEntryMap *map;
    certDBEntryListNode *node;
    int i;
    /* The first line of output always contains the cert id, subject id,
     * and nickname|smime id.  Subsequent lines may contain additional
     * cert id's for the subject if going left or both directions.
     * Ex. of printing the graph for a subject entry:
     * Cert 3 <- Subject 5 -> Nickname 32
     * Cert 8 /
     * Cert 9 /
     * means subject 5 has 3 certs, 3, 8, and 9, and corresponds
     * to nickname entry 32.
     * To accomplish the above, it is required to dump the entire first
     * line left-to-right, regardless of the input direction, and then
     * finish up any remaining cert entries.  Hence the code is uglier
     * than one may expect.
     */
    if (direction == GOLEFT || direction == GOBOTH) {
        /* In this case, nothing should be output until the first cert is
         * located and output (cert 3 in the above example).
         */
        if (subjMap->numCerts == 0 || subjMap->pCerts == NULL)
            /* XXX uh-oh */
            return;
        /* get the first cert and dump it. */
        node = subjMap->pCerts[0];
        if (map_handle_is_ok(info, (void *)node, 0)) {
            map = (certDBEntryMap *)node->appData;
            /* going left here stops. */
            print_cert_graph(info, map, GOLEFT);
        } else {
            info->dbErrors[SubjectHasNoKeyForCert]++;
        }
        /* Now it is safe to output the subject id. */
        if (direction == GOLEFT)
            printnode(info, "Subject  %5d <---- ", subjMap->index);
        else /* direction == GOBOTH */
            printnode(info, "Subject  %5d ----> ", subjMap->index);
    }
    if (direction == GORIGHT || direction == GOBOTH) {
        /* Okay, now output the nickname|smime for this subject. */
        if (direction != GOBOTH) /* handled above */
            printnode(info, "Subject  %5d ----> ", subjMap->index);
        if (subjMap->pNickname) {
            node = subjMap->pNickname;
            if (map_handle_is_ok(info, (void *)node, 0)) {
                map = (certDBEntryMap *)node->appData;
                /* going right here stops. */
                print_nickname_graph(info, map, GORIGHT);
            }
        }
        if (subjMap->pSMime) {
            node = subjMap->pSMime;
            if (map_handle_is_ok(info, (void *)node, 0)) {
                map = (certDBEntryMap *)node->appData;
                /* going right here stops. */
                print_smime_graph(info, map, GORIGHT);
            }
        }
        if (!subjMap->pNickname && !subjMap->pSMime) {
            printnode(info, "******************* ", -1);
            info->dbErrors[NoNicknameOrSMimeForSubject]++;
        }
        if (subjMap->pNickname && subjMap->pSMime) {
            info->dbErrors[NicknameAndSMimeEntries]++;
        }
    }
    if (direction != GORIGHT) { /* going right has only one cert */
        if (opttype == certDBEntryTypeNickname)
            printnode(info, "Nickname %5d   ", optindex);
        else if (opttype == certDBEntryTypeSMimeProfile)
            printnode(info, "S/MIME   %5d   ", optindex);
        for (i = 1 /* 1st one already done */; i < subjMap->numCerts; i++) {
            printnode(info, "\n", -1); /* start a new line */
            node = subjMap->pCerts[i];
            if (map_handle_is_ok(info, (void *)node, 0)) {
                map = (certDBEntryMap *)node->appData;
                /* going left here stops. */
                print_cert_graph(info, map, GOLEFT);
                printnode(info, "/", -1);
            }
        }
    }
}

/* Given a cert entry, print its unique identifer.  If GORIGHT is specified,
 * print the cert->subject->nickname|smime map, else just print
 * the cert entry.
 */
void
print_cert_graph(dbDebugInfo *info, certDBEntryMap *certMap, int direction)
{
    certDBSubjectEntryMap *subjMap;
    certDBEntryListNode *subjNode;
    if (direction == GOLEFT) {
        printnode(info, "Cert     %5d <---- ", certMap->index);
        /* only want cert entry, terminate here. */
        return;
    }
    /* Keep going right then. */
    printnode(info, "Cert     %5d ----> ", certMap->index);
    subjNode = certMap->pSubject;
    if (map_handle_is_ok(info, (void *)subjNode, 0)) {
        subjMap = (certDBSubjectEntryMap *)subjNode->appData;
        print_subject_graph(info, subjMap, GORIGHT, -1, -1);
    } else {
        info->dbErrors[NoSubjectForCert]++;
    }
}

SECStatus
computeDBGraph(certDBArray *dbArray, dbDebugInfo *info)
{
    PRCList *cElem, *sElem, *nElem, *mElem;
    certDBEntryListNode *node;
    certDBEntryMap *map;
    certDBSubjectEntryMap *subjMap;

    /* Graph is of this form:
     *
     * certs:
     * cert ---> subject ---> (nickname|smime)
     *
     * subjects:
     * cert <--- subject ---> (nickname|smime)
     *
     * nicknames and smime:
     * cert <--- subject <--- (nickname|smime)
     */

    /* Print cert graph. */
    for (cElem = PR_LIST_HEAD(&dbArray->certs.link);
         cElem != &dbArray->certs.link; cElem = PR_NEXT_LINK(cElem)) {
        /* Print graph of everything to right of cert entry. */
        node = LISTNODE_CAST(cElem);
        map = (certDBEntryMap *)node->appData;
        print_cert_graph(info, map, GORIGHT);
        printnode(info, "\n", -1);
    }
    printnode(info, "\n", -1);

    /* Print subject graph. */
    for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
         sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
        /* Print graph of everything to both sides of subject entry. */
        node = LISTNODE_CAST(sElem);
        subjMap = (certDBSubjectEntryMap *)node->appData;
        print_subject_graph(info, subjMap, GOBOTH, -1, -1);
        printnode(info, "\n", -1);
    }
    printnode(info, "\n", -1);

    /* Print nickname graph. */
    for (nElem = PR_LIST_HEAD(&dbArray->nicknames.link);
         nElem != &dbArray->nicknames.link; nElem = PR_NEXT_LINK(nElem)) {
        /* Print graph of everything to left of nickname entry. */
        node = LISTNODE_CAST(nElem);
        map = (certDBEntryMap *)node->appData;
        print_nickname_graph(info, map, GOLEFT);
        printnode(info, "\n", -1);
    }
    printnode(info, "\n", -1);

    /* Print smime graph. */
    for (mElem = PR_LIST_HEAD(&dbArray->smime.link);
         mElem != &dbArray->smime.link; mElem = PR_NEXT_LINK(mElem)) {
        /* Print graph of everything to left of smime entry. */
        node = LISTNODE_CAST(mElem);
        if (node == NULL)
            break;
        map = (certDBEntryMap *)node->appData;
        print_smime_graph(info, map, GOLEFT);
        printnode(info, "\n", -1);
    }
    printnode(info, "\n", -1);

    return SECSuccess;
}

/*
 * List the entries in the db, showing handles between entry types.
 */
void
verboseOutput(certDBArray *dbArray, dbDebugInfo *info)
{
    int i, ref;
    PRCList *elem;
    certDBEntryListNode *node;
    certDBEntryMap *map;
    certDBSubjectEntryMap *smap;
    certDBEntrySubject *subjectEntry;

    /* List certs */
    for (elem = PR_LIST_HEAD(&dbArray->certs.link);
         elem != &dbArray->certs.link; elem = PR_NEXT_LINK(elem)) {
        node = LISTNODE_CAST(elem);
        map = (certDBEntryMap *)node->appData;
        dumpCertEntry((certDBEntryCert *)&node->entry, map->index, info->out);
        /* walk the cert handle to it's subject entry */
        if (map_handle_is_ok(info, map->pSubject, -1)) {
            smap = (certDBSubjectEntryMap *)map->pSubject->appData;
            ref = smap->index;
            PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
        } else {
            PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
        }
    }
    /* List subjects */
    for (elem = PR_LIST_HEAD(&dbArray->subjects.link);
         elem != &dbArray->subjects.link; elem = PR_NEXT_LINK(elem)) {
        int refs = 0;
        node = LISTNODE_CAST(elem);
        subjectEntry = (certDBEntrySubject *)&node->entry;
        smap = (certDBSubjectEntryMap *)node->appData;
        dumpSubjectEntry(subjectEntry, smap->index, info->out);
        /* iterate over subject's certs */
        for (i = 0; i < smap->numCerts; i++) {
            /* walk each subject handle to it's cert entries */
            if (map_handle_is_ok(info, smap->pCerts[i], -1)) {
                ref = ((certDBEntryMap *)smap->pCerts[i]->appData)->index;
                PR_fprintf(info->out, "-->(%d. certificate %d)\n", i, ref);
            } else {
                PR_fprintf(info->out, "-->(%d. MISSING CERT ENTRY)\n", i);
            }
        }
        if (subjectEntry->nickname) {
            ++refs;
            /* walk each subject handle to it's nickname entry */
            if (map_handle_is_ok(info, smap->pNickname, -1)) {
                ref = ((certDBEntryMap *)smap->pNickname->appData)->index;
                PR_fprintf(info->out, "-->(nickname %d)\n", ref);
            } else {
                PR_fprintf(info->out, "-->(MISSING NICKNAME ENTRY)\n");
            }
        }
        if (subjectEntry->nemailAddrs &&
            subjectEntry->emailAddrs &&
            subjectEntry->emailAddrs[0] &&
            subjectEntry->emailAddrs[0][0]) {
            ++refs;
            /* walk each subject handle to it's smime entry */
            if (map_handle_is_ok(info, smap->pSMime, -1)) {
                ref = ((certDBEntryMap *)smap->pSMime->appData)->index;
                PR_fprintf(info->out, "-->(s/mime %d)\n", ref);
            } else {
                PR_fprintf(info->out, "-->(MISSING S/MIME ENTRY)\n");
            }
        }
        if (!refs) {
            PR_fprintf(info->out, "-->(NO NICKNAME+S/MIME ENTRY)\n");
        }
        PR_fprintf(info->out, "\n\n");
    }
    for (elem = PR_LIST_HEAD(&dbArray->nicknames.link);
         elem != &dbArray->nicknames.link; elem = PR_NEXT_LINK(elem)) {
        node = LISTNODE_CAST(elem);
        map = (certDBEntryMap *)node->appData;
        dumpNicknameEntry((certDBEntryNickname *)&node->entry, map->index,
                          info->out);
        if (map_handle_is_ok(info, map->pSubject, -1)) {
            ref = ((certDBEntryMap *)map->pSubject->appData)->index;
            PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
        } else {
            PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
        }
    }
    for (elem = PR_LIST_HEAD(&dbArray->smime.link);
         elem != &dbArray->smime.link; elem = PR_NEXT_LINK(elem)) {
        node = LISTNODE_CAST(elem);
        map = (certDBEntryMap *)node->appData;
        dumpSMimeEntry((certDBEntrySMime *)&node->entry, map->index, info->out);
        if (map_handle_is_ok(info, map->pSubject, -1)) {
            ref = ((certDBEntryMap *)map->pSubject->appData)->index;
            PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
        } else {
            PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
        }
    }
    PR_fprintf(info->out, "\n\n");
}

/* A callback function, intended to be called from nsslowcert_TraverseDBEntries
 * Builds a PRCList of DB entries of the specified type.
 */
SECStatus
SEC_GetCertDBEntryList(SECItem *dbdata, SECItem *dbkey,
                       certDBEntryType entryType, void *pdata)
{
    certDBEntry *entry;
    certDBEntryListNode *node;
    PRCList *list = (PRCList *)pdata;

    if (!dbdata || !dbkey || !pdata || !dbdata->data || !dbkey->data) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    entry = nsslowcert_DecodeAnyDBEntry(dbdata, dbkey, entryType, NULL);
    if (!entry) {
        return SECSuccess; /* skip it */
    }
    node = PORT_ArenaZNew(entry->common.arena, certDBEntryListNode);
    if (!node) {
        /* DestroyDBEntry(entry); */
        PLArenaPool *arena = entry->common.arena;
        PORT_Memset(&entry->common, 0, sizeof entry->common);
        PORT_FreeArena(arena, PR_FALSE);
        return SECFailure;
    }
    node->entry = *entry; /* crude but effective. */
    PR_INIT_CLIST(&node->link);
    PR_INSERT_BEFORE(&node->link, list);
    return SECSuccess;
}

int
fillDBEntryArray(NSSLOWCERTCertDBHandle *handle, certDBEntryType type,
                 certDBEntryListNode *list)
{
    PRCList *elem;
    certDBEntryListNode *node;
    certDBEntryMap *mnode;
    certDBSubjectEntryMap *smnode;
    PLArenaPool *arena;
    int count = 0;

    /* Initialize a dummy entry in the list.  The list head will be the
     * next element, so this element is skipped by for loops.
     */
    PR_INIT_CLIST((PRCList *)list);
    /* Collect all of the cert db entries for this type into a list. */
    nsslowcert_TraverseDBEntries(handle, type, SEC_GetCertDBEntryList, list);

    for (elem = PR_LIST_HEAD(&list->link);
         elem != &list->link; elem = PR_NEXT_LINK(elem)) {
        /* Iterate over the entries and ... */
        node = (certDBEntryListNode *)elem;
        if (type != certDBEntryTypeSubject) {
            arena = PORT_NewArena(sizeof(*mnode));
            mnode = PORT_ArenaZNew(arena, certDBEntryMap);
            mnode->arena = arena;
            /* ... assign a unique index number to each node, and ... */
            mnode->index = count;
            /* ... set the map pointer for the node. */
            node->appData = (void *)mnode;
        } else {
            /* allocate some room for the cert pointers also */
            arena = PORT_NewArena(sizeof(*smnode) + 20 * sizeof(void *));
            smnode = PORT_ArenaZNew(arena, certDBSubjectEntryMap);
            smnode->arena = arena;
            smnode->index = count;
            node->appData = (void *)smnode;
        }
        count++;
    }
    return count;
}

void
freeDBEntryList(PRCList *list)
{
    PRCList *next, *elem;
    certDBEntryListNode *node;
    certDBEntryMap *map;

    for (elem = PR_LIST_HEAD(list); elem != list;) {
        next = PR_NEXT_LINK(elem);
        node = (certDBEntryListNode *)elem;
        map = (certDBEntryMap *)node->appData;
        PR_REMOVE_LINK(&node->link);
        PORT_FreeArena(map->arena, PR_TRUE);
        PORT_FreeArena(node->entry.common.arena, PR_TRUE);
        elem = next;
    }
}

void
DBCK_DebugDB(NSSLOWCERTCertDBHandle *handle, PRFileDesc *out,
             PRFileDesc *mailfile)
{
    int i, nCertsFound, nSubjFound, nErr;
    int nCerts, nSubjects, nSubjCerts, nNicknames, nSMime, nRevocation;
    PRCList *elem;
    char c;
    dbDebugInfo info;
    certDBArray dbArray;

    PORT_Memset(&dbArray, 0, sizeof(dbArray));
    PORT_Memset(&info, 0, sizeof(info));
    info.verbose = (PRBool)(out != NULL);
    info.dograph = info.verbose;
    info.out = (out) ? out : PR_STDOUT;
    info.graphfile = mailfile ? mailfile : PR_STDOUT;

    /*  Fill the array structure with cert/subject/nickname/smime entries.  */
    dbArray.numCerts = fillDBEntryArray(handle, certDBEntryTypeCert,
                                        &dbArray.certs);
    dbArray.numSubjects = fillDBEntryArray(handle, certDBEntryTypeSubject,
                                           &dbArray.subjects);
    dbArray.numNicknames = fillDBEntryArray(handle, certDBEntryTypeNickname,
                                            &dbArray.nicknames);
    dbArray.numSMime = fillDBEntryArray(handle, certDBEntryTypeSMimeProfile,
                                        &dbArray.smime);
    dbArray.numRevocation = fillDBEntryArray(handle, certDBEntryTypeRevocation,
                                             &dbArray.revocation);

    /*  Compute the map between the database entries.  */
    mapSubjectEntries(&dbArray);
    mapCertEntries(&dbArray);
    computeDBGraph(&dbArray, &info);

    /*  Store the totals for later reference.  */
    nCerts = dbArray.numCerts;
    nSubjects = dbArray.numSubjects;
    nNicknames = dbArray.numNicknames;
    nSMime = dbArray.numSMime;
    nRevocation = dbArray.numRevocation;
    nSubjCerts = 0;
    for (elem = PR_LIST_HEAD(&dbArray.subjects.link);
         elem != &dbArray.subjects.link; elem = PR_NEXT_LINK(elem)) {
        certDBSubjectEntryMap *smap;
        smap = (certDBSubjectEntryMap *)LISTNODE_CAST(elem)->appData;
        nSubjCerts += smap->numCerts;
    }

    if (info.verbose) {
        /*  Dump the database contents.  */
        verboseOutput(&dbArray, &info);
    }

    freeDBEntryList(&dbArray.certs.link);
    freeDBEntryList(&dbArray.subjects.link);
    freeDBEntryList(&dbArray.nicknames.link);
    freeDBEntryList(&dbArray.smime.link);
    freeDBEntryList(&dbArray.revocation.link);

    PR_fprintf(info.out, "\n");
    PR_fprintf(info.out, "Database statistics:\n");
    PR_fprintf(info.out, "N0: Found %4d Certificate entries.\n",
               nCerts);
    PR_fprintf(info.out, "N1: Found %4d Subject entries (unique DN's).\n",
               nSubjects);
    PR_fprintf(info.out, "N2: Found %4d Cert keys within Subject entries.\n",
               nSubjCerts);
    PR_fprintf(info.out, "N3: Found %4d Nickname entries.\n",
               nNicknames);
    PR_fprintf(info.out, "N4: Found %4d S/MIME entries.\n",
               nSMime);
    PR_fprintf(info.out, "N5: Found %4d CRL entries.\n",
               nRevocation);
    PR_fprintf(info.out, "\n");

    nErr = 0;
    for (i = 0; i < NUM_ERROR_TYPES; i++) {
        PR_fprintf(info.out, "E%d: Found %4d %s\n",
                   i, info.dbErrors[i], errResult[i]);
        nErr += info.dbErrors[i];
    }
    PR_fprintf(info.out, "--------------\n    Found %4d errors in database.\n",
               nErr);

    PR_fprintf(info.out, "\nCertificates:\n");
    PR_fprintf(info.out, "N0 == N2 + E%d + E%d\n", NoSubjectForCert,
               SubjectHasNoKeyForCert);
    nCertsFound = nSubjCerts +
                  info.dbErrors[NoSubjectForCert] +
                  info.dbErrors[SubjectHasNoKeyForCert];
    c = (nCertsFound == nCerts) ? '=' : '!';
    PR_fprintf(info.out, "%d %c= %d + %d + %d\n", nCerts, c, nSubjCerts,
               info.dbErrors[NoSubjectForCert],
               info.dbErrors[SubjectHasNoKeyForCert]);
    PR_fprintf(info.out, "\nSubjects:\n");
    PR_fprintf(info.out,
               "N1 == N3 + N4 + E%d + E%d + E%d + E%d + E%d - E%d - E%d - E%d\n",
               NoNicknameOrSMimeForSubject,
               WrongNicknameForSubject,
               NoNicknameEntry,
               WrongSMimeForSubject,
               NoSMimeEntry,
               NoSubjectForNickname,
               NoSubjectForSMime,
               NicknameAndSMimeEntries);
    nSubjFound = nNicknames + nSMime +
                 info.dbErrors[NoNicknameOrSMimeForSubject] +
                 info.dbErrors[WrongNicknameForSubject] +
                 info.dbErrors[NoNicknameEntry] +
                 info.dbErrors[WrongSMimeForSubject] +
                 info.dbErrors[NoSMimeEntry] -
                 info.dbErrors[NoSubjectForNickname] -
                 info.dbErrors[NoSubjectForSMime] -
                 info.dbErrors[NicknameAndSMimeEntries];
    c = (nSubjFound == nSubjects) ? '=' : '!';
    PR_fprintf(info.out,
               "%2d %c= %2d + %2d + %2d + %2d + %2d + %2d + %2d - %2d - %2d - %2d\n",
               nSubjects, c, nNicknames, nSMime,
               info.dbErrors[NoNicknameOrSMimeForSubject],
               info.dbErrors[WrongNicknameForSubject],
               info.dbErrors[NoNicknameEntry],
               info.dbErrors[WrongSMimeForSubject],
               info.dbErrors[NoSMimeEntry],
               info.dbErrors[NoSubjectForNickname],
               info.dbErrors[NoSubjectForSMime],
               info.dbErrors[NicknameAndSMimeEntries]);
    PR_fprintf(info.out, "\n");
}

#ifdef DORECOVER
#include "dbrecover.c"
#endif /* DORECOVER */

enum {
    cmd_Debug = 0,
    cmd_LongUsage,
    cmd_Recover
};

enum {
    opt_KeepAll = 0,
    opt_CertDir,
    opt_Dumpfile,
    opt_InputDB,
    opt_OutputDB,
    opt_Mailfile,
    opt_Prompt,
    opt_KeepRedundant,
    opt_KeepNoSMimeProfile,
    opt_Verbose,
    opt_KeepExpired
};

static secuCommandFlag dbck_commands[] = {
    { /* cmd_Debug,    */ 'D', PR_FALSE, 0, PR_FALSE },
    { /* cmd_LongUsage,*/ 'H', PR_FALSE, 0, PR_FALSE },
    { /* cmd_Recover,  */ 'R', PR_FALSE, 0, PR_FALSE }
};

static secuCommandFlag dbck_options[] = {
    { /* opt_KeepAll,           */ 'a', PR_FALSE, 0, PR_FALSE },
    { /* opt_CertDir,           */ 'd', PR_TRUE, 0, PR_FALSE },
    { /* opt_Dumpfile,          */ 'f', PR_TRUE, 0, PR_FALSE },
    { /* opt_InputDB,           */ 'i', PR_TRUE, 0, PR_FALSE },
    { /* opt_OutputDB,          */ 'o', PR_TRUE, 0, PR_FALSE },
    { /* opt_Mailfile,          */ 'm', PR_FALSE, 0, PR_FALSE },
    { /* opt_Prompt,            */ 'p', PR_FALSE, 0, PR_FALSE },
    { /* opt_KeepRedundant,     */ 'r', PR_FALSE, 0, PR_FALSE },
    { /* opt_KeepNoSMimeProfile,*/ 's', PR_FALSE, 0, PR_FALSE },
    { /* opt_Verbose,           */ 'v', PR_FALSE, 0, PR_FALSE },
    { /* opt_KeepExpired,       */ 'x', PR_FALSE, 0, PR_FALSE }
};

#define CERT_DB_FMT "%s/cert%s.db"

static char *
dbck_certdb_name_cb(void *arg, int dbVersion)
{
    const char *configdir = (const char *)arg;
    const char *dbver;
    char *smpname = NULL;
    char *dbname = NULL;

    switch (dbVersion) {
        case 8:
            dbver = "8";
            break;
        case 7:
            dbver = "7";
            break;
        case 6:
            dbver = "6";
            break;
        case 5:
            dbver = "5";
            break;
        case 4:
        default:
            dbver = "";
            break;
    }

    /* make sure we return something allocated with PORT_ so we have properly
     * matched frees at the end */
    smpname = PR_smprintf(CERT_DB_FMT, configdir, dbver);
    if (smpname) {
        dbname = PORT_Strdup(smpname);
        PR_smprintf_free(smpname);
    }
    return dbname;
}

int
main(int argc, char **argv)
{
    NSSLOWCERTCertDBHandle *certHandle;

    PRFileDesc *mailfile = NULL;
    PRFileDesc *dumpfile = NULL;

    char *pathname = 0;
    char *fullname = 0;
    char *newdbname = 0;

    PRBool removeExpired, requireProfile, singleEntry;
    SECStatus rv;
    secuCommand dbck;

    dbck.numCommands = sizeof(dbck_commands) / sizeof(secuCommandFlag);
    dbck.numOptions = sizeof(dbck_options) / sizeof(secuCommandFlag);
    dbck.commands = dbck_commands;
    dbck.options = dbck_options;

    progName = strrchr(argv[0], '/');
    progName = progName ? progName + 1 : argv[0];

    rv = SECU_ParseCommandLine(argc, argv, progName, &dbck);

    if (rv != SECSuccess)
        Usage(progName);

    if (dbck.commands[cmd_LongUsage].activated)
        LongUsage(progName);

    if (!dbck.commands[cmd_Debug].activated &&
        !dbck.commands[cmd_Recover].activated) {
        PR_fprintf(PR_STDERR, "Please specify -H, -D or -R.\n");
        Usage(progName);
    }

    removeExpired = !(dbck.options[opt_KeepAll].activated ||
                      dbck.options[opt_KeepExpired].activated);

    requireProfile = !(dbck.options[opt_KeepAll].activated ||
                       dbck.options[opt_KeepNoSMimeProfile].activated);

    singleEntry = !(dbck.options[opt_KeepAll].activated ||
                    dbck.options[opt_KeepRedundant].activated);

    if (dbck.options[opt_OutputDB].activated) {
        newdbname = PL_strdup(dbck.options[opt_OutputDB].arg);
    } else {
        newdbname = PL_strdup("new_cert8.db");
    }

    /*  Create a generic graph of the database.  */
    if (dbck.options[opt_Mailfile].activated) {
        mailfile = PR_Open("./mailfile", PR_RDWR | PR_CREATE_FILE, 00660);
        if (!mailfile) {
            fprintf(stderr, "Unable to create mailfile.\n");
            return -1;
        }
    }

    /*  Dump all debugging info while running.  */
    if (dbck.options[opt_Verbose].activated) {
        if (dbck.options[opt_Dumpfile].activated) {
            dumpfile = PR_Open(dbck.options[opt_Dumpfile].arg,
                               PR_RDWR | PR_CREATE_FILE, 00660);
            if (!dumpfile) {
                fprintf(stderr, "Unable to create dumpfile.\n");
                return -1;
            }
        } else {
            dumpfile = PR_STDOUT;
        }
    }

    /*  Set the cert database directory.  */
    if (dbck.options[opt_CertDir].activated) {
        SECU_ConfigDirectory(dbck.options[opt_CertDir].arg);
    }

    pathname = SECU_ConfigDirectory(NULL);

    PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    rv = NSS_NoDB_Init(pathname);
    if (rv != SECSuccess) {
        fprintf(stderr, "NSS_NoDB_Init failed\n");
        return -1;
    }

    certHandle = PORT_ZNew(NSSLOWCERTCertDBHandle);
    if (!certHandle) {
        SECU_PrintError(progName, "unable to get database handle");
        return -1;
    }
    certHandle->ref = 1;

#ifdef NOTYET
    /*  Open the possibly corrupt database.  */
    if (dbck.options[opt_InputDB].activated) {
        PRFileInfo fileInfo;
        fullname = PR_smprintf("%s/%s", pathname,
                               dbck.options[opt_InputDB].arg);
        if (PR_GetFileInfo(fullname, &fileInfo) != PR_SUCCESS) {
            fprintf(stderr, "Unable to read file \"%s\".\n", fullname);
            return -1;
        }
        rv = CERT_OpenCertDBFilename(certHandle, fullname, PR_TRUE);
    } else
#endif
    {
/*  Use the default.  */
#ifdef NOTYET
        fullname = SECU_CertDBNameCallback(NULL, CERT_DB_FILE_VERSION);
        if (PR_GetFileInfo(fullname, &fileInfo) != PR_SUCCESS) {
            fprintf(stderr, "Unable to read file \"%s\".\n", fullname);
            return -1;
        }
#endif
        rv = nsslowcert_OpenCertDB(certHandle,
                                   PR_TRUE,             /* readOnly */
                                   NULL,                /* rdb appName */
                                   "",                  /* rdb prefix */
                                   dbck_certdb_name_cb, /* namecb */
                                   pathname,            /* configDir */
                                   PR_FALSE);           /* volatile */
    }

    if (rv) {
        SECU_PrintError(progName, "unable to open cert database");
        return -1;
    }

    if (dbck.commands[cmd_Debug].activated) {
        DBCK_DebugDB(certHandle, dumpfile, mailfile);
        return 0;
    }

#ifdef DORECOVER
    if (dbck.commands[cmd_Recover].activated) {
        DBCK_ReconstructDBFromCerts(certHandle, newdbname,
                                    dumpfile, removeExpired,
                                    requireProfile, singleEntry,
                                    dbck.options[opt_Prompt].activated);
        return 0;
    }
#endif

    if (mailfile)
        PR_Close(mailfile);
    if (dumpfile)
        PR_Close(dumpfile);
    if (certHandle) {
        nsslowcert_ClosePermCertDB(certHandle);
        PORT_Free(certHandle);
    }
    return -1;
}