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

#include "prtime.h"

#include "cert.h"
#include "certi.h"
#include "certdb.h"
#include "secitem.h"
#include "secder.h"

/* Call to PK11_FreeSlot below */

#include "secasn1.h"
#include "secerr.h"
#include "nssilock.h"
#include "prmon.h"
#include "base64.h"
#include "sechash.h"
#include "plhash.h"
#include "pk11func.h" /* sigh */

#include "nsspki.h"
#include "pki.h"
#include "pkim.h"
#include "pki3hack.h"
#include "ckhelper.h"
#include "base.h"
#include "pkistore.h"
#include "dev3hack.h"
#include "dev.h"

PRBool
SEC_CertNicknameConflict(const char *nickname, SECItem *derSubject,
			 CERTCertDBHandle *handle)
{
    CERTCertificate *cert;
    PRBool conflict = PR_FALSE;

    cert=CERT_FindCertByNickname(handle, nickname);

    if (!cert) {
	return conflict;
    }

    conflict = !SECITEM_ItemsAreEqual(derSubject,&cert->derSubject);
    CERT_DestroyCertificate(cert);
    return conflict;
}

SECStatus
SEC_DeletePermCertificate(CERTCertificate *cert)
{
    PRStatus nssrv;
    NSSTrustDomain *td = STAN_GetDefaultTrustDomain();
    NSSCertificate *c = STAN_GetNSSCertificate(cert);
    CERTCertTrust *certTrust;

    if (c == NULL) {
        /* error code is set */
        return SECFailure;
    }

    certTrust = nssTrust_GetCERTCertTrustForCert(c, cert);
    if (certTrust) {
	NSSTrust *nssTrust = nssTrustDomain_FindTrustForCertificate(td, c);
	if (nssTrust) {
	    nssrv = STAN_DeleteCertTrustMatchingSlot(c);
	    if (nssrv != PR_SUCCESS) {
    		CERT_MapStanError();
    	    }
	    /* This call always returns PR_SUCCESS! */
	    (void) nssTrust_Destroy(nssTrust);
	}
    }

    /* get rid of the token instances */
    nssrv = NSSCertificate_DeleteStoredObject(c, NULL);

    /* get rid of the cache entry */
    nssTrustDomain_LockCertCache(td);
    nssTrustDomain_RemoveCertFromCacheLOCKED(td, c);
    nssTrustDomain_UnlockCertCache(td);

    return (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure;
}

SECStatus
CERT_GetCertTrust(CERTCertificate *cert, CERTCertTrust *trust)
{
    SECStatus rv;
    CERT_LockCertTrust(cert);
    if ( cert->trust == NULL ) {
	rv = SECFailure;
    } else {
	*trust = *cert->trust;
	rv = SECSuccess;
    }
    CERT_UnlockCertTrust(cert);
    return(rv);
}

extern const NSSError NSS_ERROR_NO_ERROR;
extern const NSSError NSS_ERROR_INTERNAL_ERROR;
extern const NSSError NSS_ERROR_NO_MEMORY;
extern const NSSError NSS_ERROR_INVALID_POINTER;
extern const NSSError NSS_ERROR_INVALID_ARENA;
extern const NSSError NSS_ERROR_INVALID_ARENA_MARK;
extern const NSSError NSS_ERROR_DUPLICATE_POINTER;
extern const NSSError NSS_ERROR_POINTER_NOT_REGISTERED;
extern const NSSError NSS_ERROR_TRACKER_NOT_EMPTY;
extern const NSSError NSS_ERROR_TRACKER_NOT_INITIALIZED;
extern const NSSError NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD;
extern const NSSError NSS_ERROR_VALUE_TOO_LARGE;
extern const NSSError NSS_ERROR_UNSUPPORTED_TYPE;
extern const NSSError NSS_ERROR_BUFFER_TOO_SHORT;
extern const NSSError NSS_ERROR_INVALID_ATOB_CONTEXT;
extern const NSSError NSS_ERROR_INVALID_BASE64;
extern const NSSError NSS_ERROR_INVALID_BTOA_CONTEXT;
extern const NSSError NSS_ERROR_INVALID_ITEM;
extern const NSSError NSS_ERROR_INVALID_STRING;
extern const NSSError NSS_ERROR_INVALID_ASN1ENCODER;
extern const NSSError NSS_ERROR_INVALID_ASN1DECODER;
extern const NSSError NSS_ERROR_INVALID_BER;
extern const NSSError NSS_ERROR_INVALID_ATAV;
extern const NSSError NSS_ERROR_INVALID_ARGUMENT;
extern const NSSError NSS_ERROR_INVALID_UTF8;
extern const NSSError NSS_ERROR_INVALID_NSSOID;
extern const NSSError NSS_ERROR_UNKNOWN_ATTRIBUTE;
extern const NSSError NSS_ERROR_NOT_FOUND;
extern const NSSError NSS_ERROR_INVALID_PASSWORD;
extern const NSSError NSS_ERROR_USER_CANCELED;
extern const NSSError NSS_ERROR_MAXIMUM_FOUND;
extern const NSSError NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND;
extern const NSSError NSS_ERROR_CERTIFICATE_IN_CACHE;
extern const NSSError NSS_ERROR_HASH_COLLISION;
extern const NSSError NSS_ERROR_DEVICE_ERROR;
extern const NSSError NSS_ERROR_INVALID_CERTIFICATE;
extern const NSSError NSS_ERROR_BUSY;
extern const NSSError NSS_ERROR_ALREADY_INITIALIZED;
extern const NSSError NSS_ERROR_PKCS11;


/* Look at the stan error stack and map it to NSS 3 errors */
#define STAN_MAP_ERROR(x,y)   \
 else if (error == (x)) {     \
  secError = y;               \
 }                            \

/* 
 * map Stan errors into NSS errors
 * This function examines the stan error stack and automatically sets
 * PORT_SetError(); to the appropriate SEC_ERROR value.
 */
void
CERT_MapStanError()
{
    PRInt32 *errorStack;
    NSSError error, prevError;
    int secError;
    int i;

    error = 0;

    errorStack = NSS_GetErrorStack();
    if (errorStack == 0) {
	PORT_SetError(0);
	return;
    } 
    error = prevError = CKR_GENERAL_ERROR;
    /* get the 'top 2' error codes from the stack */
    for (i=0; errorStack[i]; i++) {
	prevError = error;
	error = errorStack[i];
    }
    if (error == NSS_ERROR_PKCS11) {
	/* map it */
	secError = PK11_MapError(prevError);
    }
	STAN_MAP_ERROR(NSS_ERROR_NO_ERROR, 0)
	STAN_MAP_ERROR(NSS_ERROR_NO_MEMORY, SEC_ERROR_NO_MEMORY)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_BASE64, SEC_ERROR_BAD_DATA)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_BER, SEC_ERROR_BAD_DER)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ATAV, SEC_ERROR_INVALID_AVA)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_PASSWORD,SEC_ERROR_BAD_PASSWORD)
	STAN_MAP_ERROR(NSS_ERROR_BUSY, SEC_ERROR_BUSY)
	STAN_MAP_ERROR(NSS_ERROR_DEVICE_ERROR, SEC_ERROR_IO)
	STAN_MAP_ERROR(NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND, 
			SEC_ERROR_UNKNOWN_ISSUER)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_CERTIFICATE, SEC_ERROR_CERT_NOT_VALID)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_UTF8, SEC_ERROR_BAD_DATA)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_NSSOID, SEC_ERROR_BAD_DATA)

	/* these are library failure for lack of a better error code */
	STAN_MAP_ERROR(NSS_ERROR_NOT_FOUND, SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_CERTIFICATE_IN_CACHE,
						 SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_MAXIMUM_FOUND, SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_USER_CANCELED, SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_TRACKER_NOT_INITIALIZED,
						 SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_ALREADY_INITIALIZED, SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD,
						 SEC_ERROR_LIBRARY_FAILURE)
	STAN_MAP_ERROR(NSS_ERROR_HASH_COLLISION, SEC_ERROR_LIBRARY_FAILURE)

	STAN_MAP_ERROR(NSS_ERROR_INTERNAL_ERROR, SEC_ERROR_LIBRARY_FAILURE)

	/* these are all invalid arguments */
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ARGUMENT, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_POINTER, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ARENA, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ARENA_MARK, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_DUPLICATE_POINTER, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_POINTER_NOT_REGISTERED, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_TRACKER_NOT_EMPTY, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_VALUE_TOO_LARGE, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_UNSUPPORTED_TYPE, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_BUFFER_TOO_SHORT, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ATOB_CONTEXT, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_BTOA_CONTEXT, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ITEM, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_STRING, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ASN1ENCODER, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_INVALID_ASN1DECODER, SEC_ERROR_INVALID_ARGS)
	STAN_MAP_ERROR(NSS_ERROR_UNKNOWN_ATTRIBUTE, SEC_ERROR_INVALID_ARGS)
    else {
	secError = SEC_ERROR_LIBRARY_FAILURE;
    }
    PORT_SetError(secError);
}

    

SECStatus
CERT_ChangeCertTrust(CERTCertDBHandle *handle, CERTCertificate *cert,
		    CERTCertTrust *trust)
{
    SECStatus rv = SECSuccess;
    PRStatus ret;

    CERT_LockCertTrust(cert);
    ret = STAN_ChangeCertTrust(cert, trust);
    CERT_UnlockCertTrust(cert);
    if (ret != PR_SUCCESS) {
	rv = SECFailure;
	CERT_MapStanError();
    }
    return rv;
}

extern const NSSError NSS_ERROR_INVALID_CERTIFICATE;

SECStatus
__CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname,
		       CERTCertTrust *trust)
{
    NSSUTF8 *stanNick;
    PK11SlotInfo *slot;
    NSSToken *internal;
    NSSCryptoContext *context;
    nssCryptokiObject *permInstance;
    NSSCertificate *c = STAN_GetNSSCertificate(cert);
    nssCertificateStoreTrace lockTrace = {NULL, NULL, PR_FALSE, PR_FALSE};
    nssCertificateStoreTrace unlockTrace = {NULL, NULL, PR_FALSE, PR_FALSE};
    SECStatus rv;
    PRStatus ret;

    if (c == NULL) {
	CERT_MapStanError();
        return SECFailure;
    }

    context = c->object.cryptoContext;
    if (!context) {
	PORT_SetError(SEC_ERROR_ADDING_CERT); 
	return SECFailure; /* wasn't a temp cert */
    }
    stanNick = nssCertificate_GetNickname(c, NULL);
    if (stanNick && nickname && strcmp(nickname, stanNick) != 0) {
	/* take the new nickname */
	cert->nickname = NULL;
	stanNick = NULL;
    }
    if (!stanNick && nickname) {
	stanNick = nssUTF8_Duplicate((NSSUTF8 *)nickname, c->object.arena);
    }
    /* Delete the temp instance */
    nssCertificateStore_Lock(context->certStore, &lockTrace);
    nssCertificateStore_RemoveCertLOCKED(context->certStore, c);
    nssCertificateStore_Unlock(context->certStore, &lockTrace, &unlockTrace);
    c->object.cryptoContext = NULL;
    /* Import the perm instance onto the internal token */
    slot = PK11_GetInternalKeySlot();
    internal = PK11Slot_GetNSSToken(slot);
    permInstance = nssToken_ImportCertificate(internal, NULL,
                                              NSSCertificateType_PKIX,
                                              &c->id,
                                              stanNick,
                                              &c->encoding,
                                              &c->issuer,
                                              &c->subject,
                                              &c->serial,
					      cert->emailAddr,
                                              PR_TRUE);
    PK11_FreeSlot(slot);
    if (!permInstance) {
	if (NSS_GetError() == NSS_ERROR_INVALID_CERTIFICATE) {
	    PORT_SetError(SEC_ERROR_REUSED_ISSUER_AND_SERIAL);
	}
	return SECFailure;
    }
    nssPKIObject_AddInstance(&c->object, permInstance);
    nssTrustDomain_AddCertsToCache(STAN_GetDefaultTrustDomain(), &c, 1);
    /* reset the CERTCertificate fields */
    cert->nssCertificate = NULL;
    cert = STAN_GetCERTCertificateOrRelease(c); /* should return same pointer */
    if (!cert) {
	CERT_MapStanError();
        return SECFailure;
    }
    cert->istemp = PR_FALSE;
    cert->isperm = PR_TRUE;
    if (!trust) {
	return SECSuccess;
    }
    ret = STAN_ChangeCertTrust(cert, trust);
    rv = SECSuccess;
    if (ret != PR_SUCCESS) {
	rv = SECFailure;
	CERT_MapStanError();
    }
    return rv;
}

SECStatus
CERT_AddTempCertToPerm(CERTCertificate *cert, char *nickname,
		       CERTCertTrust *trust)
{
    return __CERT_AddTempCertToPerm(cert, nickname, trust);
}

CERTCertificate *
CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert,
			char *nickname, PRBool isperm, PRBool copyDER)
{
    NSSCertificate *c;
    CERTCertificate *cc;
    NSSCertificate *tempCert = NULL;
    nssPKIObject *pkio;
    NSSCryptoContext *gCC = STAN_GetDefaultCryptoContext();
    NSSTrustDomain *gTD = STAN_GetDefaultTrustDomain();
    if (!isperm) {
	NSSDER encoding;
	NSSITEM_FROM_SECITEM(&encoding, derCert);
	/* First, see if it is already a temp cert */
	c = NSSCryptoContext_FindCertificateByEncodedCertificate(gCC, 
	                                                       &encoding);
	if (!c) {
	    /* Then, see if it is already a perm cert */
	    c = NSSTrustDomain_FindCertificateByEncodedCertificate(handle, 
	                                                           &encoding);
	}
	if (c) {
	    /* actually, that search ends up going by issuer/serial,
	     * so it is still possible to return a cert with the same
	     * issuer/serial but a different encoding, and we're
	     * going to reject that
	     */
	    if (!nssItem_Equal(&c->encoding, &encoding, NULL)) {
		nssCertificate_Destroy(c);
		PORT_SetError(SEC_ERROR_REUSED_ISSUER_AND_SERIAL);
		cc = NULL;
	    } else {
    		cc = STAN_GetCERTCertificateOrRelease(c);
		if (cc == NULL) {
		    CERT_MapStanError();
		}
	    }
	    return cc;
	}
    }
    pkio = nssPKIObject_Create(NULL, NULL, gTD, gCC, nssPKIMonitor);
    if (!pkio) {
	CERT_MapStanError();
	return NULL;
    }
    c = nss_ZNEW(pkio->arena, NSSCertificate);
    if (!c) {
	CERT_MapStanError();
	nssPKIObject_Destroy(pkio);
	return NULL;
    }
    c->object = *pkio;
    if (copyDER) {
	nssItem_Create(c->object.arena, &c->encoding, 
	               derCert->len, derCert->data);
    } else {
	NSSITEM_FROM_SECITEM(&c->encoding, derCert);
    }
    /* Forces a decoding of the cert in order to obtain the parts used
     * below
     */
    /* 'c' is not adopted here, if we fail loser frees what has been
     * allocated so far for 'c' */
    cc = STAN_GetCERTCertificate(c);
    if (!cc) {
	CERT_MapStanError();
        goto loser;
    }
    nssItem_Create(c->object.arena, 
                   &c->issuer, cc->derIssuer.len, cc->derIssuer.data);
    nssItem_Create(c->object.arena, 
                   &c->subject, cc->derSubject.len, cc->derSubject.data);
    if (PR_TRUE) {
	/* CERTCertificate stores serial numbers decoded.  I need the DER
	* here.  sigh.
	*/
	SECItem derSerial = { 0 };
	CERT_SerialNumberFromDERCert(&cc->derCert, &derSerial);
	if (!derSerial.data) goto loser;
	nssItem_Create(c->object.arena, &c->serial, derSerial.len, derSerial.data);
	PORT_Free(derSerial.data);
    }
    if (nickname) {
	c->object.tempName = nssUTF8_Create(c->object.arena, 
                                            nssStringType_UTF8String, 
                                            (NSSUTF8 *)nickname, 
                                            PORT_Strlen(nickname));
    }
    if (cc->emailAddr && cc->emailAddr[0]) {
	c->email = nssUTF8_Create(c->object.arena, 
	                          nssStringType_PrintableString, 
	                          (NSSUTF8 *)cc->emailAddr, 
	                          PORT_Strlen(cc->emailAddr));
    }

    tempCert = NSSCryptoContext_FindOrImportCertificate(gCC, c);
    if (!tempCert) {
	CERT_MapStanError();
	goto loser;
    }
    /* destroy our copy */
    NSSCertificate_Destroy(c);
    /* and use the stored entry */
    c = tempCert;
    cc = STAN_GetCERTCertificateOrRelease(c);
    if (!cc) {
	/* STAN_GetCERTCertificateOrRelease destroys c on failure. */
	CERT_MapStanError();
	return NULL;
    }

    cc->istemp = PR_TRUE;
    cc->isperm = PR_FALSE;
    return cc;
loser:
    /* Perhaps this should be nssCertificate_Destroy(c) */
    nssPKIObject_Destroy(&c->object);
    return NULL;
}

/* This symbol is exported for backward compatibility. */
CERTCertificate *
__CERT_NewTempCertificate(CERTCertDBHandle *handle, SECItem *derCert,
			  char *nickname, PRBool isperm, PRBool copyDER)
{
    return CERT_NewTempCertificate(handle, derCert, nickname,
                                   isperm, copyDER);
}

/* maybe all the wincx's should be some const for internal token login? */
CERTCertificate *
CERT_FindCertByIssuerAndSN(CERTCertDBHandle *handle, CERTIssuerAndSN *issuerAndSN)
{
    PK11SlotInfo *slot;
    CERTCertificate *cert;

    cert = PK11_FindCertByIssuerAndSN(&slot,issuerAndSN,NULL);
    if (cert && slot) {
        PK11_FreeSlot(slot);
    }

    return cert;
}

static NSSCertificate *
get_best_temp_or_perm(NSSCertificate *ct, NSSCertificate *cp)
{
    NSSUsage usage;
    NSSCertificate *arr[3];
    if (!ct) {
	return nssCertificate_AddRef(cp);
    } else if (!cp) {
	return nssCertificate_AddRef(ct);
    }
    arr[0] = ct;
    arr[1] = cp;
    arr[2] = NULL;
    usage.anyUsage = PR_TRUE;
    return nssCertificateArray_FindBestCertificate(arr, NULL, &usage, NULL);
}

CERTCertificate *
CERT_FindCertByName(CERTCertDBHandle *handle, SECItem *name)
{
    NSSCertificate *cp, *ct, *c;
    NSSDER subject;
    NSSUsage usage;
    NSSCryptoContext *cc;
    NSSITEM_FROM_SECITEM(&subject, name);
    usage.anyUsage = PR_TRUE;
    cc = STAN_GetDefaultCryptoContext();
    ct = NSSCryptoContext_FindBestCertificateBySubject(cc, &subject, 
                                                       NULL, &usage, NULL);
    cp = NSSTrustDomain_FindBestCertificateBySubject(handle, &subject, 
                                                     NULL, &usage, NULL);
    c = get_best_temp_or_perm(ct, cp);
    if (ct) {
	CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct));
    }
    if (cp) {
	CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(cp));
    }
    return c ? STAN_GetCERTCertificateOrRelease(c) : NULL;
}

CERTCertificate *
CERT_FindCertByKeyID(CERTCertDBHandle *handle, SECItem *name, SECItem *keyID)
{
    CERTCertList *list;
    CERTCertificate *cert = NULL;
    CERTCertListNode *node, *head;

    list = CERT_CreateSubjectCertList(NULL,handle,name,0,PR_FALSE);
    if (list == NULL) return NULL;

    node = head = CERT_LIST_HEAD(list);
    if (head) {
	do {
	    if (node->cert && 
		SECITEM_ItemsAreEqual(&node->cert->subjectKeyID, keyID) ) {
		cert = CERT_DupCertificate(node->cert);
		goto done;
	    }
	    node = CERT_LIST_NEXT(node);
	} while (node && head != node);
    }
    PORT_SetError(SEC_ERROR_UNKNOWN_ISSUER);
done:
    if (list) {
        CERT_DestroyCertList(list);
    }
    return cert;
}

CERTCertificate *
CERT_FindCertByNickname(CERTCertDBHandle *handle, const char *nickname)
{
    NSSCryptoContext *cc;
    NSSCertificate *c, *ct;
    CERTCertificate *cert;
    NSSUsage usage;
    usage.anyUsage = PR_TRUE;
    cc = STAN_GetDefaultCryptoContext();
    ct = NSSCryptoContext_FindBestCertificateByNickname(cc, nickname, 
                                                       NULL, &usage, NULL);
    cert = PK11_FindCertFromNickname(nickname, NULL);
    c = NULL;
    if (cert) {
	c = get_best_temp_or_perm(ct, STAN_GetNSSCertificate(cert));
	CERT_DestroyCertificate(cert);
	if (ct) {
	    CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct));
	}
    } else {
	c = ct;
    }
    return c ? STAN_GetCERTCertificateOrRelease(c) : NULL;
}

CERTCertificate *
CERT_FindCertByDERCert(CERTCertDBHandle *handle, SECItem *derCert)
{
    NSSCryptoContext *cc;
    NSSCertificate *c;
    NSSDER encoding;
    NSSITEM_FROM_SECITEM(&encoding, derCert);
    cc = STAN_GetDefaultCryptoContext();
    c = NSSCryptoContext_FindCertificateByEncodedCertificate(cc, &encoding);
    if (!c) {
	c = NSSTrustDomain_FindCertificateByEncodedCertificate(handle, 
	                                                       &encoding);
	if (!c) return NULL;
    }
    return STAN_GetCERTCertificateOrRelease(c);
}

static CERTCertificate *
common_FindCertByNicknameOrEmailAddrForUsage(CERTCertDBHandle *handle, 
                                             char *name,
                                             PRBool anyUsage,
                                             SECCertUsage lookingForUsage)
{
    NSSCryptoContext *cc;
    NSSCertificate *c, *ct;
    CERTCertificate *cert;
    NSSUsage usage;
    CERTCertList *certlist;

    if (NULL == name) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return NULL;
    }

    usage.anyUsage = anyUsage;

    if (!anyUsage) {
      usage.nss3lookingForCA = PR_FALSE;
      usage.nss3usage = lookingForUsage;
    }

    cc = STAN_GetDefaultCryptoContext();
    ct = NSSCryptoContext_FindBestCertificateByNickname(cc, name, 
                                                       NULL, &usage, NULL);
    if (!ct && PORT_Strchr(name, '@') != NULL) {
        char* lowercaseName = CERT_FixupEmailAddr(name);
        if (lowercaseName) {
	    ct = NSSCryptoContext_FindBestCertificateByEmail(cc, lowercaseName, 
							    NULL, &usage, NULL);
            PORT_Free(lowercaseName);
        }
    }

    if (anyUsage) {
      cert = PK11_FindCertFromNickname(name, NULL);
    }
    else {
      if (ct) {
        /* Does ct really have the required usage? */
          nssDecodedCert *dc;
          dc = nssCertificate_GetDecoding(ct);
          if (!dc->matchUsage(dc, &usage)) {
            CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct));
            ct = NULL;
          }
      }

      certlist = PK11_FindCertsFromNickname(name, NULL);
      if (certlist) {
        SECStatus rv = CERT_FilterCertListByUsage(certlist, 
                                                  lookingForUsage, 
                                                  PR_FALSE);
        if (SECSuccess == rv &&
            !CERT_LIST_END(CERT_LIST_HEAD(certlist), certlist)) {
          cert = CERT_DupCertificate(CERT_LIST_HEAD(certlist)->cert);
        }
        CERT_DestroyCertList(certlist);
      }
    }

    if (cert) {
	c = get_best_temp_or_perm(ct, STAN_GetNSSCertificate(cert));
	CERT_DestroyCertificate(cert);
	if (ct) {
	    CERT_DestroyCertificate(STAN_GetCERTCertificateOrRelease(ct));
	}
    } else {
	c = ct;
    }
    return c ? STAN_GetCERTCertificateOrRelease(c) : NULL;
}

CERTCertificate *
CERT_FindCertByNicknameOrEmailAddr(CERTCertDBHandle *handle, const char *name)
{
  return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, 
                                                      PR_TRUE, 0);
}

CERTCertificate *
CERT_FindCertByNicknameOrEmailAddrForUsage(CERTCertDBHandle *handle, 
                                           const char *name, 
                                           SECCertUsage lookingForUsage)
{
  return common_FindCertByNicknameOrEmailAddrForUsage(handle, name, 
                                                      PR_FALSE, 
                                                      lookingForUsage);
}

static void 
add_to_subject_list(CERTCertList *certList, CERTCertificate *cert,
                    PRBool validOnly, int64 sorttime)
{
    SECStatus secrv;
    if (!validOnly ||
	CERT_CheckCertValidTimes(cert, sorttime, PR_FALSE) 
	 == secCertTimeValid) {
	    secrv = CERT_AddCertToListSorted(certList, cert, 
	                                     CERT_SortCBValidity, 
	                                     (void *)&sorttime);
	    if (secrv != SECSuccess) {
		CERT_DestroyCertificate(cert);
	    }
    } else {
	CERT_DestroyCertificate(cert);
    }
}

CERTCertList *
CERT_CreateSubjectCertList(CERTCertList *certList, CERTCertDBHandle *handle,
			   SECItem *name, int64 sorttime, PRBool validOnly)
{
    NSSCryptoContext *cc;
    NSSCertificate **tSubjectCerts, **pSubjectCerts;
    NSSCertificate **ci;
    CERTCertificate *cert;
    NSSDER subject;
    PRBool myList = PR_FALSE;
    cc = STAN_GetDefaultCryptoContext();
    NSSITEM_FROM_SECITEM(&subject, name);
    /* Collect both temp and perm certs for the subject */
    tSubjectCerts = NSSCryptoContext_FindCertificatesBySubject(cc,
                                                               &subject,
                                                               NULL,
                                                               0,
                                                               NULL);
    pSubjectCerts = NSSTrustDomain_FindCertificatesBySubject(handle,
                                                             &subject,
                                                             NULL,
                                                             0,
                                                             NULL);
    if (!tSubjectCerts && !pSubjectCerts) {
	return NULL;
    }
    if (certList == NULL) {
	certList = CERT_NewCertList();
	myList = PR_TRUE;
	if (!certList) goto loser;
    }
    /* Iterate over the matching temp certs.  Add them to the list */
    ci = tSubjectCerts;
    while (ci && *ci) {
	cert = STAN_GetCERTCertificateOrRelease(*ci);
	/* *ci may be invalid at this point, don't reference it again */
        if (cert) {
	    /* NOTE: add_to_subject_list adopts the incoming cert. */
	    add_to_subject_list(certList, cert, validOnly, sorttime);
        }
	ci++;
    }
    /* Iterate over the matching perm certs.  Add them to the list */
    ci = pSubjectCerts;
    while (ci && *ci) {
	cert = STAN_GetCERTCertificateOrRelease(*ci);
	/* *ci may be invalid at this point, don't reference it again */
        if (cert) {
	    /* NOTE: add_to_subject_list adopts the incoming cert. */
	    add_to_subject_list(certList, cert, validOnly, sorttime);
        }
	ci++;
    }
    /* all the references have been adopted or freed at this point, just
     * free the arrays now */
    nss_ZFreeIf(tSubjectCerts);
    nss_ZFreeIf(pSubjectCerts);
    return certList;
loser:
    /* need to free the references in tSubjectCerts and pSubjectCerts! */
    nssCertificateArray_Destroy(tSubjectCerts);
    nssCertificateArray_Destroy(pSubjectCerts);
    if (myList && certList != NULL) {
	CERT_DestroyCertList(certList);
    }
    return NULL;
}

void
CERT_DestroyCertificate(CERTCertificate *cert)
{
    if ( cert ) {
	/* don't use STAN_GetNSSCertificate because we don't want to
	 * go to the trouble of translating the CERTCertificate into
	 * an NSSCertificate just to destroy it.  If it hasn't been done
	 * yet, don't do it at all.
	 */
	NSSCertificate *tmp = cert->nssCertificate;
	if (tmp) {
	    /* delete the NSSCertificate */
	    NSSCertificate_Destroy(tmp);
	} else if (cert->arena) {
	    PORT_FreeArena(cert->arena, PR_FALSE);
	}
    }
    return;
}

int
CERT_GetDBContentVersion(CERTCertDBHandle *handle)
{
    /* should read the DB content version from the pkcs #11 device */
    return 0;
}

SECStatus
certdb_SaveSingleProfile(CERTCertificate *cert, const char *emailAddr, 
				SECItem *emailProfile, SECItem *profileTime)
{
    int64 oldtime;
    int64 newtime;
    SECStatus rv = SECFailure;
    PRBool saveit;
    SECItem oldprof, oldproftime;
    SECItem *oldProfile = NULL;
    SECItem *oldProfileTime = NULL;
    PK11SlotInfo *slot = NULL;
    NSSCertificate *c;
    NSSCryptoContext *cc;
    nssSMIMEProfile *stanProfile = NULL;
    PRBool freeOldProfile = PR_FALSE;

    c = STAN_GetNSSCertificate(cert);
    if (!c) return SECFailure;
    cc = c->object.cryptoContext;
    if (cc != NULL) {
	stanProfile = nssCryptoContext_FindSMIMEProfileForCertificate(cc, c);
	if (stanProfile) {
	    PORT_Assert(stanProfile->profileData);
	    SECITEM_FROM_NSSITEM(&oldprof, stanProfile->profileData);
	    oldProfile = &oldprof;
	    SECITEM_FROM_NSSITEM(&oldproftime, stanProfile->profileTime);
	    oldProfileTime = &oldproftime;
	}
    } else {
	oldProfile = PK11_FindSMimeProfile(&slot, (char *)emailAddr, 
					&cert->derSubject, &oldProfileTime); 
	freeOldProfile = PR_TRUE;
    }

    saveit = PR_FALSE;
    
    /* both profileTime and emailProfile have to exist or not exist */
    if ( emailProfile == NULL ) {
	profileTime = NULL;
    } else if ( profileTime == NULL ) {
	emailProfile = NULL;
    }
   
    if ( oldProfileTime == NULL ) {
	saveit = PR_TRUE;
    } else {
	/* there was already a profile for this email addr */
	if ( profileTime ) {
	    /* we have an old and new profile - save whichever is more recent*/
	    if ( oldProfileTime->len == 0 ) {
		/* always replace if old entry doesn't have a time */
		oldtime = LL_MININT;
	    } else {
		rv = DER_UTCTimeToTime(&oldtime, oldProfileTime);
		if ( rv != SECSuccess ) {
		    goto loser;
		}
	    }
	    
	    rv = DER_UTCTimeToTime(&newtime, profileTime);
	    if ( rv != SECSuccess ) {
		goto loser;
	    }
	
	    if ( LL_CMP(newtime, >, oldtime ) ) {
		/* this is a newer profile, save it and cert */
		saveit = PR_TRUE;
	    }
	} else {
	    saveit = PR_TRUE;
	}
    }


    if (saveit) {
	if (cc) {
	    if (stanProfile) {
		/* stanProfile is already stored in the crypto context,
		 * overwrite the data
		 */
		NSSArena *arena = stanProfile->object.arena;
		stanProfile->profileTime = nssItem_Create(arena, 
		                                          NULL,
		                                          profileTime->len,
		                                          profileTime->data);
		stanProfile->profileData = nssItem_Create(arena, 
		                                          NULL,
		                                          emailProfile->len,
		                                          emailProfile->data);
	    } else if (profileTime && emailProfile) {
		PRStatus nssrv;
		NSSItem profTime, profData;
		NSSITEM_FROM_SECITEM(&profTime, profileTime);
		NSSITEM_FROM_SECITEM(&profData, emailProfile);
		stanProfile = nssSMIMEProfile_Create(c, &profTime, &profData);
		if (!stanProfile) goto loser;
		nssrv = nssCryptoContext_ImportSMIMEProfile(cc, stanProfile);
		rv = (nssrv == PR_SUCCESS) ? SECSuccess : SECFailure;
	    }
	} else {
	    rv = PK11_SaveSMimeProfile(slot, (char *)emailAddr, 
				&cert->derSubject, emailProfile, profileTime);
	}
    } else {
	rv = SECSuccess;
    }

loser:
    if (oldProfile && freeOldProfile) {
    	SECITEM_FreeItem(oldProfile,PR_TRUE);
    }
    if (oldProfileTime && freeOldProfile) {
    	SECITEM_FreeItem(oldProfileTime,PR_TRUE);
    }
    if (stanProfile) {
	nssSMIMEProfile_Destroy(stanProfile);
    }
    if (slot) {
	PK11_FreeSlot(slot);
    }
    
    return(rv);
}

/*
 *
 * Manage S/MIME profiles
 *
 */

SECStatus
CERT_SaveSMimeProfile(CERTCertificate *cert, SECItem *emailProfile,
		      SECItem *profileTime)
{
    const char *emailAddr;
    SECStatus rv;

    if (!cert) {
        return SECFailure;
    }

    if (cert->slot &&  !PK11_IsInternal(cert->slot)) {
        /* this cert comes from an external source, we need to add it
        to the cert db before creating an S/MIME profile */
        PK11SlotInfo* internalslot = PK11_GetInternalKeySlot();
        if (!internalslot) {
            return SECFailure;
        }
        rv = PK11_ImportCert(internalslot, cert,
            CK_INVALID_HANDLE, NULL, PR_FALSE);

        PK11_FreeSlot(internalslot);
        if (rv != SECSuccess ) {
            return SECFailure;
        }
    }

    if (cert->slot && cert->isperm && CERT_IsUserCert(cert) &&
	(!emailProfile || !emailProfile->len)) {
	/* Don't clobber emailProfile for user certs. */
    	return SECSuccess;
    }

    for (emailAddr = CERT_GetFirstEmailAddress(cert); emailAddr != NULL;
		emailAddr = CERT_GetNextEmailAddress(cert,emailAddr)) {
	rv = certdb_SaveSingleProfile(cert,emailAddr,emailProfile,profileTime);
	if (rv != SECSuccess) {
	   return SECFailure;
	}
    }
    return SECSuccess;

}


SECItem *
CERT_FindSMimeProfile(CERTCertificate *cert)
{
    PK11SlotInfo *slot = NULL;
    NSSCertificate *c;
    NSSCryptoContext *cc;
    SECItem *rvItem = NULL;

    if (!cert || !cert->emailAddr || !cert->emailAddr[0]) {
	PORT_SetError(SEC_ERROR_INVALID_ARGS);
	return NULL;
    }
    c = STAN_GetNSSCertificate(cert);
    if (!c) return NULL;
    cc = c->object.cryptoContext;
    if (cc != NULL) {
	nssSMIMEProfile *stanProfile;
	stanProfile = nssCryptoContext_FindSMIMEProfileForCertificate(cc, c);
	if (stanProfile) {
	    rvItem = SECITEM_AllocItem(NULL, NULL, 
	                               stanProfile->profileData->size);
	    if (rvItem) {
		rvItem->data = stanProfile->profileData->data;
	    }
	    nssSMIMEProfile_Destroy(stanProfile);
	}
	return rvItem;
    }
    rvItem =
	PK11_FindSMimeProfile(&slot, cert->emailAddr, &cert->derSubject, NULL);
    if (slot) {
    	PK11_FreeSlot(slot);
    }
    return rvItem;
}

/*
 * deprecated functions that are now just stubs.
 */
/*
 * Close the database
 */
void
__CERT_ClosePermCertDB(CERTCertDBHandle *handle)
{
    PORT_Assert("CERT_ClosePermCertDB is Deprecated" == NULL);
    return;
}

SECStatus
CERT_OpenCertDBFilename(CERTCertDBHandle *handle, char *certdbname,
                        PRBool readOnly)
{
    PORT_Assert("CERT_OpenCertDBFilename is Deprecated" == NULL);
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
    return SECFailure;
}

SECItem *
SECKEY_HashPassword(char *pw, SECItem *salt)
{
    PORT_Assert("SECKEY_HashPassword is Deprecated" == NULL);
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
    return NULL;
}

SECStatus
__CERT_TraversePermCertsForSubject(CERTCertDBHandle *handle,
                                 SECItem *derSubject,
                                 void *cb, void *cbarg)
{
    PORT_Assert("CERT_TraversePermCertsForSubject is Deprecated" == NULL);
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
    return SECFailure;
}


SECStatus
__CERT_TraversePermCertsForNickname(CERTCertDBHandle *handle, char *nickname,
                                  void *cb, void *cbarg)
{
    PORT_Assert("CERT_TraversePermCertsForNickname is Deprecated" == NULL);
    PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
    return SECFailure;
}



