Back to index

lightning-sunbird  0.9+nobinonly
Classes | Typedefs | Enumerations | Functions
dbrecover.c File Reference

Go to the source code of this file.

Classes

struct  dbRestoreInfoStr

Typedefs

typedef struct dbRestoreInfoStr dbRestoreInfo

Enumerations

enum  {
  dbInvalidCert = 0, dbNoSMimeProfile, dbOlderCert, dbBadCertificate,
  dbCertNotWrittenToDB
}

Functions

char * IsEmailCert (CERTCertificate *cert)
SECStatus deleteit (CERTCertificate *cert, void *arg)
SECStatus deleteAllEntriesForCert (NSSLOWCERTCertDBHandle *handle, CERTCertificate *cert, PRFileDesc *outfile)
void getCertsToDelete (char *numlist, int len, int *certNums, int nCerts)
PRBool userSaysDeleteCert (CERTCertificate **certs, int nCerts, int errtype, dbRestoreInfo *info, int *certNums)
SECStatus addCertToDB (certDBEntryCert *certEntry, dbRestoreInfo *info, NSSLOWCERTCertDBHandle *oldhandle)
int certIsOlder (CERTCertificate **cert1, CERTCertificate **cert2)
int findNewestSubjectForEmail (NSSLOWCERTCertDBHandle *handle, int subjectNum, certDBArray *dbArray, dbRestoreInfo *info, int *subjectWithSMime, int *smimeForSubject)
NSSLOWCERTCertDBHandle * DBCK_ReconstructDBFromCerts (NSSLOWCERTCertDBHandle *oldhandle, char *newdbname, PRFileDesc *outfile, PRBool removeExpired, PRBool requireProfile, PRBool singleEntry, PRBool promptUser)

Class Documentation

struct dbRestoreInfoStr

Definition at line 45 of file dbrecover.c.

Collaboration diagram for dbRestoreInfoStr:
Class Members
int dbErrors
NSSLOWCERTCertDBHandle * handle
int nCerts
int nOldCerts
PRFileDesc * out
PRBool promptUser
PRBool removeType
PRBool verbose

Typedef Documentation


Enumeration Type Documentation

anonymous enum
Enumerator:
dbInvalidCert 
dbNoSMimeProfile 
dbOlderCert 
dbBadCertificate 
dbCertNotWrittenToDB 

Definition at line 37 of file dbrecover.c.


Function Documentation

SECStatus addCertToDB ( certDBEntryCert *  certEntry,
dbRestoreInfo info,
NSSLOWCERTCertDBHandle *  oldhandle 
)

Definition at line 274 of file dbrecover.c.

{
    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;
}

Here is the call graph for this function:

int certIsOlder ( CERTCertificate **  cert1,
CERTCertificate **  cert2 
)

Definition at line 462 of file dbrecover.c.

{
    return !CERT_IsNewer(*cert1, *cert2);
}

Here is the call graph for this function:

NSSLOWCERTCertDBHandle* DBCK_ReconstructDBFromCerts ( NSSLOWCERTCertDBHandle *  oldhandle,
char *  newdbname,
PRFileDesc outfile,
PRBool  removeExpired,
PRBool  requireProfile,
PRBool  singleEntry,
PRBool  promptUser 
)

Definition at line 587 of file dbrecover.c.

{
    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;
}

Here is the call graph for this function:

SECStatus deleteAllEntriesForCert ( NSSLOWCERTCertDBHandle *  handle,
CERTCertificate *  cert,
PRFileDesc outfile 
)

Definition at line 124 of file dbrecover.c.

{
#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;
}

Here is the call graph for this function:

SECStatus deleteit ( CERTCertificate *  cert,
void arg 
)

Definition at line 115 of file dbrecover.c.

Here is the call graph for this function:

int findNewestSubjectForEmail ( NSSLOWCERTCertDBHandle *  handle,
int  subjectNum,
certDBArray dbArray,
dbRestoreInfo info,
int subjectWithSMime,
int smimeForSubject 
)

Definition at line 468 of file dbrecover.c.

{
    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;
}

Here is the call graph for this function:

void getCertsToDelete ( char *  numlist,
int  len,
int certNums,
int  nCerts 
)

Definition at line 177 of file dbrecover.c.

{
    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");
    }
}
char* IsEmailCert ( CERTCertificate *  cert)

Definition at line 58 of file dbrecover.c.

{
    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;
}

Here is the call graph for this function:

PRBool userSaysDeleteCert ( CERTCertificate **  certs,
int  nCerts,
int  errtype,
dbRestoreInfo info,
int certNums 
)

Definition at line 205 of file dbrecover.c.

{
    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;
}

Here is the call graph for this function: