/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
** 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 
{
    PRArenaPool *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
{
    PRArenaPool *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.  */
	int64 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;
    PRArenaPool *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;
    PRArenaPool *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;
}
