gecko/security/nss/cmd/dbck/dbrecover.c

703 lines
22 KiB
C
Raw Normal View History

2008-06-06 05:40:11 -07:00
/* ***** 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 ***** */
enum {
dbInvalidCert = 0,
dbNoSMimeProfile,
dbOlderCert,
dbBadCertificate,
dbCertNotWrittenToDB
};
typedef struct dbRestoreInfoStr
{
NSSLOWCERTCertDBHandle *handle;
PRBool verbose;
PRFileDesc *out;
int nCerts;
int nOldCerts;
int dbErrors[5];
PRBool removeType[3];
PRBool promptUser[3];
} dbRestoreInfo;
char *
IsEmailCert(CERTCertificate *cert)
{
char *email, *tmp1, *tmp2;
PRBool isCA;
int len;
if (!cert->subjectName) {
return NULL;
}
tmp1 = PORT_Strstr(cert->subjectName, "E=");
tmp2 = PORT_Strstr(cert->subjectName, "MAIL=");
/* XXX Nelson has cert for KTrilli which does not have either
* of above but is email cert (has cert->emailAddr).
*/
if (!tmp1 && !tmp2 && !(cert->emailAddr && cert->emailAddr[0])) {
return NULL;
}
/* Server or CA cert, not personal email. */
isCA = CERT_IsCACert(cert, NULL);
if (isCA)
return NULL;
/* XXX CERT_IsCACert advertises checking the key usage ext.,
but doesn't appear to. */
/* Check the key usage extension. */
if (cert->keyUsagePresent) {
/* Must at least be able to sign or encrypt (not neccesarily
* both if it is one of a dual cert).
*/
if (!((cert->rawKeyUsage & KU_DIGITAL_SIGNATURE) ||
(cert->rawKeyUsage & KU_KEY_ENCIPHERMENT)))
return NULL;
/* CA cert, not personal email. */
if (cert->rawKeyUsage & (KU_KEY_CERT_SIGN | KU_CRL_SIGN))
return NULL;
}
if (cert->emailAddr && cert->emailAddr[0]) {
email = PORT_Strdup(cert->emailAddr);
} else {
if (tmp1)
tmp1 += 2; /* "E=" */
else
tmp1 = tmp2 + 5; /* "MAIL=" */
len = strcspn(tmp1, ", ");
email = (char*)PORT_Alloc(len+1);
PORT_Strncpy(email, tmp1, len);
email[len] = '\0';
}
return email;
}
SECStatus
deleteit(CERTCertificate *cert, void *arg)
{
return SEC_DeletePermCertificate(cert);
}
/* Different than DeleteCertificate - has the added bonus of removing
* all certs with the same DN.
*/
SECStatus
deleteAllEntriesForCert(NSSLOWCERTCertDBHandle *handle, CERTCertificate *cert,
PRFileDesc *outfile)
{
#if 0
certDBEntrySubject *subjectEntry;
certDBEntryNickname *nicknameEntry;
certDBEntrySMime *smimeEntry;
int i;
#endif
if (outfile) {
PR_fprintf(outfile, "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n\n");
PR_fprintf(outfile, "Deleting redundant certificate:\n");
dumpCertificate(cert, -1, outfile);
}
CERT_TraverseCertsForSubject(handle, cert->subjectList, deleteit, NULL);
#if 0
CERT_LockDB(handle);
subjectEntry = ReadDBSubjectEntry(handle, &cert->derSubject);
/* It had better be there, or created a bad db. */
PORT_Assert(subjectEntry);
for (i=0; i<subjectEntry->ncerts; i++) {
DeleteDBCertEntry(handle, &subjectEntry->certKeys[i]);
}
DeleteDBSubjectEntry(handle, &cert->derSubject);
if (subjectEntry->emailAddr && subjectEntry->emailAddr[0]) {
smimeEntry = ReadDBSMimeEntry(handle, subjectEntry->emailAddr);
if (smimeEntry) {
if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject,
&smimeEntry->subjectName))
/* Only delete it if it's for this subject! */
DeleteDBSMimeEntry(handle, subjectEntry->emailAddr);
SEC_DestroyDBEntry((certDBEntry*)smimeEntry);
}
}
if (subjectEntry->nickname) {
nicknameEntry = ReadDBNicknameEntry(handle, subjectEntry->nickname);
if (nicknameEntry) {
if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject,
&nicknameEntry->subjectName))
/* Only delete it if it's for this subject! */
DeleteDBNicknameEntry(handle, subjectEntry->nickname);
SEC_DestroyDBEntry((certDBEntry*)nicknameEntry);
}
}
SEC_DestroyDBEntry((certDBEntry*)subjectEntry);
CERT_UnlockDB(handle);
#endif
return SECSuccess;
}
void
getCertsToDelete(char *numlist, int len, int *certNums, int nCerts)
{
int j, num;
char *numstr, *numend, *end;
numstr = numlist;
end = numstr + len - 1;
while (numstr != end) {
numend = strpbrk(numstr, ", \n");
*numend = '\0';
if (PORT_Strlen(numstr) == 0)
return;
num = PORT_Atoi(numstr);
if (numstr == numlist)
certNums[0] = num;
for (j=1; j<nCerts+1; j++) {
if (num == certNums[j]) {
certNums[j] = -1;
break;
}
}
if (numend == end)
break;
numstr = strpbrk(numend+1, "0123456789");
}
}
PRBool
userSaysDeleteCert(CERTCertificate **certs, int nCerts,
int errtype, dbRestoreInfo *info, int *certNums)
{
char response[32];
int32 nb;
int i;
/* User wants to remove cert without prompting. */
if (info->promptUser[errtype] == PR_FALSE)
return (info->removeType[errtype]);
switch (errtype) {
case dbInvalidCert:
PR_fprintf(PR_STDOUT, "******** Expired ********\n");
PR_fprintf(PR_STDOUT, "Cert has expired.\n\n");
dumpCertificate(certs[0], -1, PR_STDOUT);
PR_fprintf(PR_STDOUT,
"Keep it? (y/n - this one, Y/N - all expired certs) [n] ");
break;
case dbNoSMimeProfile:
PR_fprintf(PR_STDOUT, "******** No Profile ********\n");
PR_fprintf(PR_STDOUT, "S/MIME cert has no profile.\n\n");
dumpCertificate(certs[0], -1, PR_STDOUT);
PR_fprintf(PR_STDOUT,
"Keep it? (y/n - this one, Y/N - all S/MIME w/o profile) [n] ");
break;
case dbOlderCert:
PR_fprintf(PR_STDOUT, "******* Redundant nickname/email *******\n\n");
PR_fprintf(PR_STDOUT, "These certs have the same nickname/email:\n");
for (i=0; i<nCerts; i++)
dumpCertificate(certs[i], i, PR_STDOUT);
PR_fprintf(PR_STDOUT,
"Enter the certs you would like to keep from those listed above.\n");
PR_fprintf(PR_STDOUT,
"Use a comma-separated list of the cert numbers (ex. 0, 8, 12).\n");
PR_fprintf(PR_STDOUT,
"The first cert in the list will be the primary cert\n");
PR_fprintf(PR_STDOUT,
" accessed by the nickname/email handle.\n");
PR_fprintf(PR_STDOUT,
"List cert numbers to keep here, or hit enter\n");
PR_fprintf(PR_STDOUT,
" to always keep only the newest cert: ");
break;
default:
}
nb = PR_Read(PR_STDIN, response, sizeof(response));
PR_fprintf(PR_STDOUT, "\n\n");
if (errtype == dbOlderCert) {
if (!isdigit(response[0])) {
info->promptUser[errtype] = PR_FALSE;
info->removeType[errtype] = PR_TRUE;
return PR_TRUE;
}
getCertsToDelete(response, nb, certNums, nCerts);
return PR_TRUE;
}
/* User doesn't want to be prompted for this type anymore. */
if (response[0] == 'Y') {
info->promptUser[errtype] = PR_FALSE;
info->removeType[errtype] = PR_FALSE;
return PR_FALSE;
} else if (response[0] == 'N') {
info->promptUser[errtype] = PR_FALSE;
info->removeType[errtype] = PR_TRUE;
return PR_TRUE;
}
return (response[0] != 'y') ? PR_TRUE : PR_FALSE;
}
SECStatus
addCertToDB(certDBEntryCert *certEntry, dbRestoreInfo *info,
NSSLOWCERTCertDBHandle *oldhandle)
{
SECStatus rv = SECSuccess;
PRBool allowOverride;
PRBool userCert;
SECCertTimeValidity validity;
CERTCertificate *oldCert = NULL;
CERTCertificate *dbCert = NULL;
CERTCertificate *newCert = NULL;
CERTCertTrust *trust;
certDBEntrySMime *smimeEntry = NULL;
char *email = NULL;
char *nickname = NULL;
int nCertsForSubject = 1;
oldCert = CERT_DecodeDERCertificate(&certEntry->derCert, PR_FALSE,
certEntry->nickname);
if (!oldCert) {
info->dbErrors[dbBadCertificate]++;
SEC_DestroyDBEntry((certDBEntry*)certEntry);
return SECSuccess;
}
oldCert->dbEntry = certEntry;
oldCert->trust = &certEntry->trust;
oldCert->dbhandle = oldhandle;
trust = oldCert->trust;
info->nOldCerts++;
if (info->verbose)
PR_fprintf(info->out, "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n");
if (oldCert->nickname)
nickname = PORT_Strdup(oldCert->nickname);
/* Always keep user certs. Skip ahead. */
/* XXX if someone sends themselves a signed message, it is possible
for their cert to be imported as an "other" cert, not a user cert.
this mucks with smime entries... */
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 (userCert)
goto createcert;
/* If user chooses so, ignore expired certificates. */
allowOverride = (PRBool)((oldCert->keyUsage == certUsageSSLServer) ||
(oldCert->keyUsage == certUsageSSLServerWithStepUp));
validity = CERT_CheckCertValidTimes(oldCert, PR_Now(), allowOverride);
/* If cert expired and user wants to delete it, ignore it. */
if ((validity != secCertTimeValid) &&
userSaysDeleteCert(&oldCert, 1, dbInvalidCert, info, 0)) {
info->dbErrors[dbInvalidCert]++;
if (info->verbose) {
PR_fprintf(info->out, "Deleting expired certificate:\n");
dumpCertificate(oldCert, -1, info->out);
}
goto cleanup;
}
/* New database will already have default certs, don't attempt
to overwrite them. */
dbCert = CERT_FindCertByDERCert(info->handle, &oldCert->derCert);
if (dbCert) {
info->nCerts++;
if (info->verbose) {
PR_fprintf(info->out, "Added certificate to database:\n");
dumpCertificate(oldCert, -1, info->out);
}
goto cleanup;
}
/* Determine if cert is S/MIME and get its email if so. */
email = IsEmailCert(oldCert);
/*
XXX Just create empty profiles?
if (email) {
SECItem *profile = CERT_FindSMimeProfile(oldCert);
if (!profile &&
userSaysDeleteCert(&oldCert, 1, dbNoSMimeProfile, info, 0)) {
info->dbErrors[dbNoSMimeProfile]++;
if (info->verbose) {
PR_fprintf(info->out,
"Deleted cert missing S/MIME profile.\n");
dumpCertificate(oldCert, -1, info->out);
}
goto cleanup;
} else {
SECITEM_FreeItem(profile);
}
}
*/
createcert:
/* Sometimes happens... */
if (!nickname && userCert)
nickname = PORT_Strdup(oldCert->subjectName);
/* Create a new certificate, copy of the old one. */
newCert = CERT_NewTempCertificate(info->handle, &oldCert->derCert,
nickname, PR_FALSE, PR_TRUE);
if (!newCert) {
PR_fprintf(PR_STDERR, "Unable to create new certificate.\n");
dumpCertificate(oldCert, -1, PR_STDERR);
info->dbErrors[dbBadCertificate]++;
goto cleanup;
}
/* Add the cert to the new database. */
rv = CERT_AddTempCertToPerm(newCert, nickname, oldCert->trust);
if (rv) {
PR_fprintf(PR_STDERR, "Failed to write temp cert to perm database.\n");
dumpCertificate(oldCert, -1, PR_STDERR);
info->dbErrors[dbCertNotWrittenToDB]++;
goto cleanup;
}
if (info->verbose) {
PR_fprintf(info->out, "Added certificate to database:\n");
dumpCertificate(oldCert, -1, info->out);
}
/* If the cert is an S/MIME cert, and the first with it's subject,
* modify the subject entry to include the email address,
* CERT_AddTempCertToPerm does not do email addresses and S/MIME entries.
*/
if (smimeEntry) { /*&& !userCert && nCertsForSubject == 1) { */
#if 0
UpdateSubjectWithEmailAddr(newCert, email);
#endif
SECItem emailProfile, profileTime;
rv = CERT_FindFullSMimeProfile(oldCert, &emailProfile, &profileTime);
/* calls UpdateSubjectWithEmailAddr */
if (rv == SECSuccess)
rv = CERT_SaveSMimeProfile(newCert, &emailProfile, &profileTime);
}
info->nCerts++;
cleanup:
if (nickname)
PORT_Free(nickname);
if (email)
PORT_Free(email);
if (oldCert)
CERT_DestroyCertificate(oldCert);
if (dbCert)
CERT_DestroyCertificate(dbCert);
if (newCert)
CERT_DestroyCertificate(newCert);
if (smimeEntry)
SEC_DestroyDBEntry((certDBEntry*)smimeEntry);
return SECSuccess;
}
#if 0
SECStatus
copyDBEntry(SECItem *data, SECItem *key, certDBEntryType type, void *pdata)
{
SECStatus rv;
NSSLOWCERTCertDBHandle *newdb = (NSSLOWCERTCertDBHandle *)pdata;
certDBEntryCommon common;
SECItem dbkey;
common.type = type;
common.version = CERT_DB_FILE_VERSION;
common.flags = data->data[2];
common.arena = NULL;
dbkey.len = key->len + SEC_DB_KEY_HEADER_LEN;
dbkey.data = (unsigned char *)PORT_Alloc(dbkey.len*sizeof(unsigned char));
PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], key->data, key->len);
dbkey.data[0] = type;
rv = WriteDBEntry(newdb, &common, &dbkey, data);
PORT_Free(dbkey.data);
return rv;
}
#endif
int
certIsOlder(CERTCertificate **cert1, CERTCertificate** cert2)
{
return !CERT_IsNewer(*cert1, *cert2);
}
int
findNewestSubjectForEmail(NSSLOWCERTCertDBHandle *handle, int subjectNum,
certDBArray *dbArray, dbRestoreInfo *info,
int *subjectWithSMime, int *smimeForSubject)
{
int newestSubject;
int subjectsForEmail[50];
int i, j, ns, sNum;
certDBEntryListNode *subjects = &dbArray->subjects;
certDBEntryListNode *smime = &dbArray->smime;
certDBEntrySubject *subjectEntry1, *subjectEntry2;
certDBEntrySMime *smimeEntry;
CERTCertificate **certs;
CERTCertificate *cert;
CERTCertTrust *trust;
PRBool userCert;
int *certNums;
ns = 0;
subjectEntry1 = (certDBEntrySubject*)&subjects.entries[subjectNum];
subjectsForEmail[ns++] = subjectNum;
*subjectWithSMime = -1;
*smimeForSubject = -1;
newestSubject = subjectNum;
cert = CERT_FindCertByKey(handle, &subjectEntry1->certKeys[0]);
if (cert) {
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);
CERT_DestroyCertificate(cert);
}
/*
* XXX Should we make sure that subjectEntry1->emailAddr is not
* a null pointer or an empty string before going into the next
* two for loops, which pass it to PORT_Strcmp?
*/
/* Loop over the remaining subjects. */
for (i=subjectNum+1; i<subjects.numEntries; i++) {
subjectEntry2 = (certDBEntrySubject*)&subjects.entries[i];
if (!subjectEntry2)
continue;
if (subjectEntry2->emailAddr && subjectEntry2->emailAddr[0] &&
PORT_Strcmp(subjectEntry1->emailAddr,
subjectEntry2->emailAddr) == 0) {
/* Found a subject using the same email address. */
subjectsForEmail[ns++] = i;
}
}
/* Find the S/MIME entry for this email address. */
for (i=0; i<smime.numEntries; i++) {
smimeEntry = (certDBEntrySMime*)&smime.entries[i];
if (smimeEntry->common.arena == NULL)
continue;
if (smimeEntry->emailAddr && smimeEntry->emailAddr[0] &&
PORT_Strcmp(subjectEntry1->emailAddr, smimeEntry->emailAddr) == 0) {
/* Find which of the subjects uses this S/MIME entry. */
for (j=0; j<ns && *subjectWithSMime < 0; j++) {
sNum = subjectsForEmail[j];
subjectEntry2 = (certDBEntrySubject*)&subjects.entries[sNum];
if (SECITEM_ItemsAreEqual(&smimeEntry->subjectName,
&subjectEntry2->derSubject)) {
/* Found the subject corresponding to the S/MIME entry. */
*subjectWithSMime = sNum;
*smimeForSubject = i;
}
}
SEC_DestroyDBEntry((certDBEntry*)smimeEntry);
PORT_Memset(smimeEntry, 0, sizeof(certDBEntry));
break;
}
}
if (ns <= 1)
return subjectNum;
if (userCert)
return *subjectWithSMime;
/* Now find which of the subjects has the newest cert. */
certs = (CERTCertificate**)PORT_Alloc(ns*sizeof(CERTCertificate*));
certNums = (int*)PORT_Alloc((ns+1)*sizeof(int));
certNums[0] = 0;
for (i=0; i<ns; i++) {
sNum = subjectsForEmail[i];
subjectEntry1 = (certDBEntrySubject*)&subjects.entries[sNum];
certs[i] = CERT_FindCertByKey(handle, &subjectEntry1->certKeys[0]);
certNums[i+1] = i;
}
/* Sort the array by validity. */
qsort(certs, ns, sizeof(CERTCertificate*),
(int (*)(const void *, const void *))certIsOlder);
newestSubject = -1;
for (i=0; i<ns; i++) {
sNum = subjectsForEmail[i];
subjectEntry1 = (certDBEntrySubject*)&subjects.entries[sNum];
if (SECITEM_ItemsAreEqual(&subjectEntry1->derSubject,
&certs[0]->derSubject))
newestSubject = sNum;
else
SEC_DestroyDBEntry((certDBEntry*)subjectEntry1);
}
if (info && userSaysDeleteCert(certs, ns, dbOlderCert, info, certNums)) {
for (i=1; i<ns+1; i++) {
if (certNums[i] >= 0 && certNums[i] != certNums[0]) {
deleteAllEntriesForCert(handle, certs[certNums[i]], info->out);
info->dbErrors[dbOlderCert]++;
}
}
}
CERT_DestroyCertArray(certs, ns);
return newestSubject;
}
NSSLOWCERTCertDBHandle *
DBCK_ReconstructDBFromCerts(NSSLOWCERTCertDBHandle *oldhandle, char *newdbname,
PRFileDesc *outfile, PRBool removeExpired,
PRBool requireProfile, PRBool singleEntry,
PRBool promptUser)
{
SECStatus rv;
dbRestoreInfo info;
certDBEntryContentVersion *oldContentVersion;
certDBArray dbArray;
int i;
PORT_Memset(&dbArray, 0, sizeof(dbArray));
PORT_Memset(&info, 0, sizeof(info));
info.verbose = (outfile) ? PR_TRUE : PR_FALSE;
info.out = (outfile) ? outfile : PR_STDOUT;
info.removeType[dbInvalidCert] = removeExpired;
info.removeType[dbNoSMimeProfile] = requireProfile;
info.removeType[dbOlderCert] = singleEntry;
info.promptUser[dbInvalidCert] = promptUser;
info.promptUser[dbNoSMimeProfile] = promptUser;
info.promptUser[dbOlderCert] = promptUser;
/* Allocate a handle to fill with CERT_OpenCertDB below. */
info.handle = PORT_ZNew(NSSLOWCERTCertDBHandle);
if (!info.handle) {
fprintf(stderr, "unable to get database handle");
return NULL;
}
/* Create a certdb with the most recent set of roots. */
rv = CERT_OpenCertDBFilename(info.handle, newdbname, PR_FALSE);
if (rv) {
fprintf(stderr, "could not open certificate database");
goto loser;
}
/* Create certificate, subject, nickname, and email records.
* mcom_db seems to have a sequential access bug. Though reads and writes
* should be allowed during traversal, they seem to screw up the sequence.
* So, stuff all the cert entries into an array, and loop over the array
* doing read/writes in the db.
*/
fillDBEntryArray(oldhandle, certDBEntryTypeCert, &dbArray.certs);
for (elem = PR_LIST_HEAD(&dbArray->certs.link);
elem != &dbArray->certs.link; elem = PR_NEXT_LINK(elem)) {
node = LISTNODE_CAST(elem);
addCertToDB((certDBEntryCert*)&node->entry, &info, oldhandle);
/* entries get destroyed in addCertToDB */
}
#if 0
rv = nsslowcert_TraverseDBEntries(oldhandle, certDBEntryTypeSMimeProfile,
copyDBEntry, info.handle);
#endif
/* Fix up the pointers between (nickname|S/MIME) --> (subject).
* Create S/MIME entries for S/MIME certs.
* Have the S/MIME entry point to the last-expiring cert using
* an email address.
*/
#if 0
CERT_RedoHandlesForSubjects(info.handle, singleEntry, &info);
#endif
freeDBEntryList(&dbArray.certs.link);
/* Copy over the version record. */
/* XXX Already exists - and _must_ be correct... */
/*
versionEntry = ReadDBVersionEntry(oldhandle);
rv = WriteDBVersionEntry(info.handle, versionEntry);
*/
/* Copy over the content version record. */
/* XXX Can probably get useful info from old content version?
* Was this db created before/after this tool? etc.
*/
#if 0
oldContentVersion = ReadDBContentVersionEntry(oldhandle);
CERT_SetDBContentVersion(oldContentVersion->contentVersion, info.handle);
#endif
#if 0
/* Copy over the CRL & KRL records. */
rv = nsslowcert_TraverseDBEntries(oldhandle, certDBEntryTypeRevocation,
copyDBEntry, info.handle);
/* XXX Only one KRL, just do db->get? */
rv = nsslowcert_TraverseDBEntries(oldhandle, certDBEntryTypeKeyRevocation,
copyDBEntry, info.handle);
#endif
PR_fprintf(info.out, "Database had %d certificates.\n", info.nOldCerts);
PR_fprintf(info.out, "Reconstructed %d certificates.\n", info.nCerts);
PR_fprintf(info.out, "(ax) Rejected %d expired certificates.\n",
info.dbErrors[dbInvalidCert]);
PR_fprintf(info.out, "(as) Rejected %d S/MIME certificates missing a profile.\n",
info.dbErrors[dbNoSMimeProfile]);
PR_fprintf(info.out, "(ar) Rejected %d certificates for which a newer certificate was found.\n",
info.dbErrors[dbOlderCert]);
PR_fprintf(info.out, " Rejected %d corrupt certificates.\n",
info.dbErrors[dbBadCertificate]);
PR_fprintf(info.out, " Rejected %d certificates which did not write to the DB.\n",
info.dbErrors[dbCertNotWrittenToDB]);
if (rv)
goto loser;
return info.handle;
loser:
if (info.handle)
PORT_Free(info.handle);
return NULL;
}