/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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 Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */
#include "certlist.h"
#include "nlsutil.h"
#include "base64.h"
#include "cert.h"
#include "certdb.h"
#include "textgen.h"
#include "minihttp.h"
#include "advisor.h"
#include "slist.h"
#include "nspr.h"
#include "nlslayer.h"

enum
{
    CERTLIST_PARAM_KEY = (PRIntn) 0,
    CERTLIST_PARAM_USAGE,
    CERTLIST_PARAM_PREFIX,
    /* CERTLIST_PARAM_WRAPPER,  -jp not used, as we format all certs together */
    CERTLIST_PARAM_SUFFIX,
    CERTLIST_PARAM_COUNT
};

SSMSortedListFn list_functions;

#define WRAPPER "cert_list_item"
#define EMAIL_WRAPPER "cert_list_email_item"

typedef enum
{
    clNick,
    clCertID, /* issuer and serial number */
    clRID,
    clEmail
} ssmCLCertKey;

typedef struct 
{
    SSMTextGenContext *cx;
    char *output;
    char *temp;
    char *fmt;
    char *efmt;
    void *datefmt;
    CERTCertDBHandle * db;
    SSMSortedList **hash; /* Hash to contain list of certs to display */
    ssmCLCertKey key;
    ssmCLCertUsage usage;
} ssmCLState;

static SSMStatus format_raw_cert(CERTCertificate *cert, char *wrapper,
                                 void*dfmt, char **result);
/* Hash table functions */

PRIntn ssm_certhash_rid_compare(const void *v1, const void *v2);
PLHashNumber ssm_certhash_issuersn_hash(const void *v);
PRIntn ssm_certhash_issuersn_compare(const void *v1, const void *v2);
PRIntn ssm_pointer_compare(const void *v1, const void *v2);
PRIntn ssm_certhash_cert_compare(const void *v1, const void *v2);
void * ssm_certhash_alloc_table(void *state, PRSize size);
void ssm_certhash_free_table(void *state, void *item);
PLHashEntry * ssm_certhash_alloc_entry(void *state, const void *key);
void ssm_certhash_free_entry(void *s, PLHashEntry *he, PRUintn flag);
void certlist_free_item(SECItem *item);
void certlist_dereturnify(char *s);
SSMStatus ssm_certlist_get_b64_cert_key(CERTCertificate *cert, char **result);
void * certlist_get_cert_key(ssmCLState *state, CERTCertificate *cert, ssmCLCertUsage usage);
PRBool certlist_include_cert(CERTCertificate * cert, ssmCLState *state, 
                      ssmCLCertUsage * usage);
SECStatus certlist_get_cert(CERTCertificate *cert, SECItem *dbkey, void *arg);
SSMStatus certlist_display_cert(PRIntn index, void * arg, 
                                void * key, void * itemdata);
PRIntn certlist_wrap_cert(PLHashEntry *he, PRIntn index, void *arg);
SSMStatus certlist_get_usage(ssmCLState *state);
SSMStatus certlist_get_key_type(ssmCLState *state);
SSMStatus ssm_populate_key_hash(ssmCLState * state);

PRIntn
certlist_rid_compare(const void *v1, const void *v2)
{
  SSMResourceID rid1 = (SSMResourceID) v1;
    SSMResourceID rid2 = (SSMResourceID) v2;
    PRIntn rv;

    if (rid1 < rid2)
	rv = -1;
    else if (rid1 > rid2)
	rv = 1;
    else
	rv = 0;

    return rv;
}

PRIntn 
certlist_compare_strings(const void * v1, const void *v2)
{
    return PL_strcasecmp((char *)v1, (char *)v2);
}

PRIntn 
certlist_issuersn_compare(const void *v1, const void *v2)
{ 
    PRIntn rv;
    SECComparison cmp = SECLessThan; /* this is arbitrary */

    const CERTIssuerAndSN *i1 = (const CERTIssuerAndSN *) v1;
    const CERTIssuerAndSN *i2 = (const CERTIssuerAndSN *) v2;

    PR_ASSERT(i1);
    PR_ASSERT(i2);
    if (!i1 || !i2)
	goto loser;

    cmp = SECITEM_CompareItem(&(i1->derIssuer), &(i2->derIssuer));
    if (cmp == SECEqual)
	cmp = SECITEM_CompareItem(&(i2->serialNumber), &(i2->serialNumber));

 loser:
    /* Don't need to free i1 and i2, since they are allocated out of
       the certs' arenas. They will be freed when the certs are freed. */

    rv = (PRIntn) cmp;
    return rv;
}

/* Free ops for cert list */
void
certlist_free_data(void * data)
{
    /* Free the cert, then (if asked) free the entry. */
    PR_Free(((ssmCertData *)data)->certEntry);
    PR_Free((ssmCertData *)data);
}

void certlist_none(void * data)
{
    return;
}

void certlist_free_string(void * key)
{
    PR_Free((char *)key);
}

/* Utility routines for cert list keyword handler below */
void
certlist_free_item(SECItem *item)
{
    if (item)
    {
	if (item->data)
	    PR_Free(item->data);
	PR_Free(item);
    }
}

void
certlist_dereturnify(char *s)
{
    char *c = s;

    while(*c)
    {
        if ((*c == '\015') || (*c == '\012'))
        {
            char *c2 = c;
            while ((*c2 == '\015') || (*c2 == '\012'))
                c2++;
            /* move the rest of the string on top of c */
            memmove(c, c2, strlen(c2));
        }
	c++;
    }
}

SSMStatus
ssm_certlist_get_b64_cert_key(CERTCertificate *cert, char **result)
{
    SECItem *certKeyItem;
    CERTIssuerAndSN *isn = NULL;
    SSMStatus rv = SSM_SUCCESS;

    PR_ASSERT(cert && result);
    *result = NULL; /* in case we fail */

    isn = CERT_GetCertIssuerAndSN(NULL, cert);
    if (!isn)
	goto loser;

    certKeyItem = (SECItem *) PR_CALLOC(sizeof(SECItem));
    if (!certKeyItem)
	goto loser;

    certKeyItem->len = isn->serialNumber.len + isn->derIssuer.len;
    certKeyItem->data = (unsigned char*)PR_CALLOC(certKeyItem->len);
    if (certKeyItem->data == NULL)
	goto loser;

    /* copy the serialNumber */
    memcpy(certKeyItem->data, isn->serialNumber.data,
	   isn->serialNumber.len);

    /* copy the issuer */
    memcpy( &(certKeyItem->data[isn->serialNumber.len]),
	    isn->derIssuer.data, isn->derIssuer.len);

    /* b64 the item */
    *result = BTOA_ConvertItemToAscii(certKeyItem);
    certlist_dereturnify(*result);
    goto done;
 loser:
    rv = SSM_FAILURE;
 done:
    certlist_free_item(certKeyItem);
    return rv;
}

/* get the appropriate key depending on what key type we have chosen. */
void *
certlist_get_cert_key(ssmCLState *state, CERTCertificate *cert, ssmCLCertUsage usage)
{
    char *result_ch = NULL;
    void *result = NULL;
    SSMStatus rv = SSM_SUCCESS;

    PR_ASSERT(cert);
    if (!cert) 
        return result;

    switch(state->key)
    {
    case clCertID:
	rv = ssm_certlist_get_b64_cert_key(cert, &result_ch);
	PR_ASSERT(rv == SSM_SUCCESS);
	result = result_ch;
	break;
    case clRID:
	PR_ASSERT(!"Not yet implemented.");
	break;
    case clNick:
        /* duplicate the nickname */
        if (usage == clEmailRecipient) {
            PR_ASSERT(cert && cert->emailAddr);
            result = PR_CALLOC(strlen(cert->emailAddr)+1);
            if (result)
                PL_strcpy((char*)result, cert->emailAddr);
        } else {
            PR_ASSERT(cert && cert->nickname);
            result = PR_CALLOC(strlen(cert->nickname)+1);
            if (result)
                PL_strcpy((char *) result, cert->nickname);
        }
        break;   
    case clEmail:
        PR_ASSERT(cert && cert->emailAddr);
        result = PR_CALLOC(strlen(cert->emailAddr)+1);
        if (result)
            PL_strcpy((char*)result, cert->emailAddr);
        break;
    default:
        PR_ASSERT(0); /* scream if we defaulted here */
    }
    return result;
}


PRBool
certlist_include_cert(CERTCertificate * cert, ssmCLState *state, 
                      ssmCLCertUsage * usage)
{
    PRBool usercert = PR_FALSE;
    char *nickname;
    char *emailAddr;
    CERTCertTrust *trust;
    ssmCLCertUsage thisUsage = clNoUsage;

    /* jsw said: look at the base cert info stuff, not the dbEntry since
     * we may have been called from a PKCS#11 module */
    nickname = cert->nickname;
    trust = cert->trust;
    emailAddr = cert->emailAddr;

    /* Don't display certs that are invisible */
    if ( ( ( trust->sslFlags & CERTDB_INVISIBLE_CA ) ||
           (trust->emailFlags & CERTDB_INVISIBLE_CA ) ||
           (trust->objectSigningFlags & CERTDB_INVISIBLE_CA ) ) ) 
        goto dontInclude;
    
    if (nickname) {
        /* 
           it's a cert I have named. Could be a personal cert of mine,
           an SSL server cert, or a CA cert. Find out what kind.
        */

        if (state->usage == clSSLClient) {
            if (trust->sslFlags & CERTDB_USER) {
                /* It's an SSL cert I own. */
                thisUsage = clSSLClient;
                goto include;
            }
            else {
                goto dontInclude;
            }
        }

        if (state->usage == clEmailSigner) {
            if (trust->emailFlags & CERTDB_USER) {
                /* It's an email cert I own. */
                thisUsage = clEmailSigner;
                goto include;
            }
            else {
                goto dontInclude;
            }
        }

        if (state->usage == clAllMine) {
            if ((trust->sslFlags & CERTDB_USER) ||
                (trust->emailFlags & CERTDB_USER) ||
                (trust->objectSigningFlags & CERTDB_USER)) {
                /* It's a cert I own. */
                usercert = PR_TRUE;
                thisUsage = clAllMine;
                goto include;
            }
            else {
                goto dontInclude;
            }
        }
            
        /* it is an SSL site */
        if (trust->sslFlags & CERTDB_VALID_PEER) {
            thisUsage = clSSLServer;
            goto include;
        }
            
        /* CA certs */
        if ((trust->sslFlags & CERTDB_VALID_CA) ||
            (trust->emailFlags & CERTDB_VALID_CA) ||
            (trust->objectSigningFlags & CERTDB_VALID_CA)) { 
            thisUsage = clAllCA;
            goto include;
        }
    } /* end of if nickname */

    /* Check if this is an email cert that belongs to someone else. */
    if (emailAddr && !usercert && (trust->emailFlags & CERTDB_VALID_PEER)) {
        /* Someone else's email cert */
        thisUsage = clEmailRecipient;
        goto include;
    }
    
dontInclude:
    *usage = thisUsage;
    return PR_FALSE;

include:
    *usage = thisUsage;
    if (state->usage == thisUsage ||
        (state->usage == clAllMine &&
         (thisUsage == clSSLClient || thisUsage == clEmailSigner))) {        
        return PR_TRUE;
    }
    return PR_FALSE;
}


   
/* build/append a cert list string depending on key and usage */
SECStatus 
certlist_get_cert(CERTCertificate *cert, SECItem *dbkey, void *arg)
{
    ssmCLState *state = (ssmCLState *) arg;
    SECStatus rv = SECSuccess;
    void *certHashKey; /* the key we use to insert into the hash */ 
    ssmCLCertUsage thisUsage; 
    ssmCertData * data;
    char * certInfo = NULL, *nick = NULL;
    char * wrapper;
    

    /* check if we want to include cert in our hash table */
    if (!certlist_include_cert(cert, state, &thisUsage))
        goto done;

    /* check if cert is already in the db */
    nick = (cert->nickname)?cert->nickname:cert->emailAddr;
    if (SSMSortedList_Lookup(*state->hash, nick))
        goto done;
        
    /* different wrapper for email certs */
    if (thisUsage == clEmailRecipient)
        wrapper = state->efmt;
    else wrapper = state->fmt;

    /* If we get here, then we should include this cert in the hash table. */
    rv = (SECStatus) 
    	(format_raw_cert(cert, wrapper, state->datefmt, &certInfo) == SSM_SUCCESS) ?
		SECSuccess : SECFailure;
    if (rv != SECSuccess)
        goto done;   
    certHashKey = certlist_get_cert_key(state, cert, thisUsage);
    
    data = (ssmCertData *) PORT_ZAlloc(sizeof(ssmCertData));
    data->usage = thisUsage;
    data->certEntry = certInfo;
    /* XXX the following code is necessary because we want to pick out
     *     email signing certs among "my certs"
     */
    if (cert->trust->emailFlags & CERTDB_USER) {
        data->isEmailSigner = PR_TRUE;
    }
    else {
        data->isEmailSigner = PR_FALSE;
    }

    SSMSortedList_Insert(*state->hash, certHashKey, data);
done:
    return rv;
}

static char * 
escape_quotes(const char * str)
{
    char * outstr = NULL, * ptr = NULL;
    int i=0, length = 0;
    int quotechar = 0;

    if (!str) 
        goto done;
    ptr = str;
    while (*ptr) {
       length++;
       if (*ptr == 39 || *ptr == 34)
           quotechar++;
       ptr++;
    }
    outstr = (char *) PORT_ZAlloc(length+quotechar+1);
    if (!quotechar) {
        memcpy(outstr, str, length);
        goto done;
    }
    ptr = str;
    i = 0;
    while (i < length+quotechar) {
        if (*ptr == 39 || *ptr == 34) 
            outstr[i++] = 92;
        outstr[i++] = *ptr;
        ptr++;
    }
        
 done:
    return outstr;
}


/* Format a raw cert into (wrapper). */
static SSMStatus
format_raw_cert(CERTCertificate *cert, char *wrapper,
                void *dfmt, char **result)
{
    char *nickStr = NULL;
    char *emailStr = NULL;
    char *subjectName = NULL;
    char *validStartStr = NULL;
    char *validEndStr = NULL;
    char * issuerName = NULL;
    void *dateFormat = dfmt;
    char *isn_ch = NULL;
    SSMStatus rv = SSM_SUCCESS;

    PR_ASSERT(wrapper);

    /* If a date format wasn't supplied, make one. */
    if (dateFormat)
    {
		dateFormat = nlsNewDateFormat();
		if (!dateFormat) {
			goto loser;
		}
    }

    /* Get the key from the cert in b64 format */
    rv = ssm_certlist_get_b64_cert_key(cert, &isn_ch);
    if (rv != SSM_SUCCESS)
	goto loser;

    PR_ASSERT(isn_ch);
    nickStr  = (cert->nickname)  ?escape_quotes(cert->nickname):PL_strdup("-");
    emailStr = (cert->emailAddr) ?escape_quotes(cert->emailAddr):PL_strdup("-");
    subjectName =  (cert->subjectName) ? escape_quotes(cert->subjectName) : 
        PL_strdup("-");
    issuerName = CERT_GetOrgName(&cert->issuer);
    issuerName = issuerName ? escape_quotes(issuerName) : PL_strdup("-");
        
    validStartStr = DER_UTCDayToAscii(&cert->validity.notBefore);
    if (!validStartStr) 
        validStartStr = PL_strdup("");
    validEndStr = DER_UTCDayToAscii(&cert->validity.notAfter);
    if (!validEndStr) 
        validEndStr = PL_strdup("");

    SSMTextGen_UTF8StringClear(result);

    *result = PR_smprintf(wrapper, isn_ch, nickStr, emailStr, subjectName, 
                          validStartStr, validEndStr,
                          issuerName, 0);
    SSM_DebugUTF8String("wrapped cert", *result);
    goto done;
 loser:
    SSM_DEBUG("Couldn't format cert!");
    if (rv == SSM_SUCCESS)
        rv = SSM_FAILURE;
 done:
    PR_FREEIF(nickStr);
    PR_FREEIF(emailStr);
    PR_FREEIF(subjectName);
    PR_FREEIF(issuerName);
    PR_FREEIF(isn_ch);
    PR_FREEIF(validStartStr);
    PR_FREEIF(validEndStr);
    if (dateFormat && !dfmt) /* if we made the date format, delete it */
        nlsFreeDateFormat(dateFormat);
    return rv;
}

/* Enumerator for cert hash entries. Embed cert information in
   the wrapper (state->wrapper). */
SSMStatus 
certlist_display_cert(PRIntn index, void * arg, void * key, void * itemdata)
{
    ssmCLState *state = (ssmCLState *) arg;
    SSMStatus rv = SSM_SUCCESS;
    ssmCertData * data = (ssmCertData *) itemdata; 
    
    if (state->usage == data->usage ||
        (state->usage == clAllMine && 
         (data->usage == clSSLClient || data->usage == clEmailSigner)) ||
        (state->usage == clEmailSigner && data->usage == clAllMine &&
         data->isEmailSigner)) { 
        rv = SSM_ConcatenateUTF8String(&state->output, data->certEntry);
        if (rv != SSM_SUCCESS)
            goto loser;
    }
    goto done;
loser:
    SSM_DEBUG("certlist_display_cert: couldn't add cert info to display list!");
done:
    /* keep going */
    return SSM_SUCCESS;
}    

/* get the certType parameter */
SSMStatus
certlist_get_usage(ssmCLState *state)
{
    char *usageStr = (char *) SSM_At(state->cx->m_params, CERTLIST_PARAM_USAGE);
    SSMStatus rv = SSM_SUCCESS;

    PR_ASSERT(usageStr);
    if (!usageStr)
    {
        SSM_HTTPReportSpecificError(state->cx->m_request, 
                                    "certlist_get_usage: "
                                    "usage parameter is NULL");
        goto loser;
    }

    /* Figure out which one it is. */
    if (!MIN_STRCMP(usageStr, "AllMine"))
        state->usage = clAllMine;
    else if (!MIN_STRCMP(usageStr, "SSLC"))
        state->usage = clSSLClient;
    else if (!MIN_STRCMP(usageStr, "EmailS"))
        state->usage = clEmailSigner;
    else if (!MIN_STRCMP(usageStr, "SSLS"))
        state->usage = clSSLServer;
    else if (!MIN_STRCMP(usageStr, "EmailR"))
        state->usage = clEmailRecipient;
    else if (!MIN_STRCMP(usageStr, "CA"))
        state->usage = clAllCA;
    else /* bad value */
    {
        SSM_HTTPReportSpecificError(state->cx->m_request,
                                    "_certList: Bad usage -- must be "
                                    "AllMine|SSLClient|EmailSigner|"
                                    "SSLServer|EmailRecipient|CA");
        goto loser;
    }
    goto done;
loser:
    if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
done:
    return rv;
}

/* get the certType parameter */
SSMStatus
certlist_get_key_type(ssmCLState *state)
{
    char *keyStr = (char *) SSM_At(state->cx->m_params, CERTLIST_PARAM_KEY);
    SSMStatus rv = SSM_SUCCESS;

    PR_ASSERT(keyStr);
    if (!keyStr)
    {
        SSM_HTTPReportSpecificError(state->cx->m_request, 
                                    "certlist_get_key_type: "
                                    "key parameter is NULL");
        goto loser;
    }

    /* Figure out which one it is. */
    if (!MIN_STRCMP(keyStr, "nick"))
        state->key = clNick;
    else if (!MIN_STRCMP(keyStr, "certID"))
        state->key = clCertID;
    else if ((!MIN_STRCMP(keyStr, "rid"))
             ||(!MIN_STRCMP(keyStr, "RID")))
        state->key = clRID;
    else if (!MIN_STRCMP(keyStr, "email"))
        state->key = clEmail;
    else/* bad value */
    {
        SSM_HTTPReportSpecificError(state->cx->m_request, 
                                    "_certList: Bad cert key -- must be "
                                    "nick|certID|RID");
        goto loser;
    }
    goto done;
loser:
    if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
done:
    return rv;
}

SSMStatus
ssm_populate_key_hash(ssmCLState * state)
{
    SSMCompare_fn  keyComparer = NULL;
    SSMListFree_fn keyFree = NULL;
    SSMListFree_fn dataFree = certlist_free_data;
    SECStatus srv;
    SSMStatus rv = SSM_SUCCESS;
    char *tempUStr = NULL;
    PermCertCallback callback = certlist_get_cert;

    /* Based on the usage, choose hasher/key comparer functions for
       the hash table we create below. */
    switch(state->key)
        {
        case clRID:
            keyComparer = certlist_rid_compare;
            keyFree = certlist_none;
            break;
        case clEmail:
        case clCertID:
        case clNick:
            keyComparer = certlist_compare_strings;
            keyFree = certlist_free_string;
            break;
        default:
            PR_ASSERT(0); 
        }
    list_functions.keyCompare = keyComparer;
    list_functions.freeListItemData = dataFree;
    list_functions.freeListItemKey = keyFree;
    /* Create the hash table if needed */
    if (!*state->hash)
        *state->hash = SSMSortedList_New(&list_functions);

    if (!*state->hash) 
        goto loser;

    PR_ASSERT(state->db);
    
    /* Get the wrapper text. */
    rv = SSM_GetAndExpandTextKeyedByString(state->cx, WRAPPER, &tempUStr);
    if (rv != SSM_SUCCESS)
        goto loser; /* error string set by the called function */
    SSM_DebugUTF8String("Certlist wrapper", tempUStr);
    state->fmt = tempUStr;
    tempUStr = NULL;
    /* wrapper for email certs */
    rv = SSM_GetAndExpandTextKeyedByString(state->cx, EMAIL_WRAPPER, &tempUStr);
    if (rv != SSM_SUCCESS)
        goto loser; /* error string set by the called function */
    SSM_DebugUTF8String("Certlist email wrapper", tempUStr);
    state->efmt = tempUStr;
    tempUStr = NULL;
    /* 
       Create a DateFormat object which we will use to convert
       cert date/times into displayable text.
     */
	state->datefmt = nlsNewDateFormat();
	if (!state->datefmt) {
		goto loser;
	}

    /* Format each cert into the list. */
    srv = SEC_TraversePermCerts(state->db, callback, state);
    if (srv == SECSuccess) 
        srv = PK11_TraverseSlotCerts(callback, state, 
                                     state->cx->m_request->ctrlconn);   
    goto done;
    
loser:
    if (rv == SSM_SUCCESS)
        rv = SSM_FAILURE;
done: 
    return rv;
}

/* 
   Cert list keyword handler.
   Syntax: {_certList <certKey>,<certUsage>,prefix,wrapper,suffix}

   where <certKey>  ::= "nick" | "certID" | "RID" | "email"
         <certUsage>::= "AllMine"|"SSLClient"|"EmailSigner"|
                        "SSLServer"|"EmailRecipient"|"CA"
                        (for now)

   Generates a list of certificates. The following steps are performed:
   - Send {prefix} to the client.
   - For each cert indicated by <certType> and <certUsage>, format the 
     cert data into {wrapper} and send to the client. For the moment, 
     raw certs are used and passed to NLS_MessageFormat in the 
     following form:
         NLS_FORMAT_TYPE_STRING, (cert issuer+sn base64),
         NLS_FORMAT_TYPE_STRING, (cert nick),
         NLS_FORMAT_TYPE_STRING, (cert email address),
         NLS_FORMAT_TYPE_STRING, (cert subject name),
         NLS_FORMAT_TYPE_LONG,   (cert resource ID, 0 for now)
   - After the certs are processed and sent, send {suffix}.
*/

SSMStatus
SSM_CertListKeywordHandler(SSMTextGenContext *cx)
{
    SSMStatus rv = SSM_SUCCESS;
    char *prefix = NULL, *suffix = NULL;
    char *tempUStr = NULL;
    SSMResource * target = NULL;
    ssmCLState state;
    SSMResourceType targetType = SSM_RESTYPE_NULL;
    PRIntn certType[clAllCA+1];
    SSMSortedList *nullHash = NULL;

    certType[clAllMine] =        0x00000001;
    certType[clEmailRecipient] = 0x00000010;
    certType[clSSLServer] =      0x00000100;
    certType[clAllCA] =          0x00001000;
    certType[clEmailSigner] =    0x00010000;

    /* Check for parameter validity */
    PR_ASSERT(cx);
    PR_ASSERT(cx->m_request);
    PR_ASSERT(cx->m_params);
    PR_ASSERT(cx->m_result);
    if (!cx || !cx->m_request || !cx->m_params || !cx->m_result)
    {
        rv = (SSMStatus) PR_INVALID_ARGUMENT_ERROR;
        goto real_loser; /* really bail here */
    }

    state.cx = cx;
    state.fmt = NULL;
    state.efmt = NULL;
    state.datefmt = NULL;
    state.output = NULL;
    state.temp = NULL;
    state.db = cx->m_request->ctrlconn->m_certdb;
    if (!state.db)
    {
        SSM_HTTPReportSpecificError(state.cx->m_request, 
                                    "_certlist: no cert db available");
        goto user_loser;
    }

    if (SSM_Count(cx->m_params) != CERTLIST_PARAM_COUNT)
    {
        SSM_HTTPReportSpecificError(cx->m_request, "_certList: "
                                    "Incorrect number of parameters "
                                    "(%d supplied, %d needed).\n",
                                    SSM_Count(cx->m_params), 
                                    CERTLIST_PARAM_COUNT);
        goto user_loser;
    }

    /* Convert parameters to something we can use in finding certs. */
    rv = certlist_get_key_type(&state);
    if (rv != SSM_SUCCESS)
        goto user_loser;

    rv = certlist_get_usage(&state);
    if (rv != SSM_SUCCESS)
        goto user_loser;

    prefix = (char *) SSM_At(cx->m_params, CERTLIST_PARAM_PREFIX);
    suffix = (char *) SSM_At(cx->m_params, CERTLIST_PARAM_SUFFIX);
    PR_ASSERT(prefix); /* already did user check, so bomb here if null */
    PR_ASSERT(suffix);

    /* check where we're called from */
    target = SSMTextGen_GetTargetObject(cx);
    if (!target) 
        goto real_loser;
    /* SecurityAdvisor context keeps hash around, otherwise create new one */
    if (SSM_IsAKindOf(target, SSM_RESTYPE_SECADVISOR_CONTEXT)) {
        targetType = SSM_RESTYPE_SECADVISOR_CONTEXT;
        state.hash = &((SSMSecurityAdvisorContext *)target)->m_certhash;
    }
    else 
        state.hash = &nullHash;

    state.output = state.temp = NULL;

    /* Start with the prefix. */
    rv = SSM_GetAndExpandTextKeyedByString(cx, prefix, &tempUStr);
    if (rv != SSM_SUCCESS)
        goto real_loser; /* error string set by the called function */
    SSM_DebugUTF8String("Certlist prefix", tempUStr);
    rv = SSM_ConcatenateUTF8String(&cx->m_result, tempUStr);
    PR_Free(tempUStr);
    tempUStr = NULL;
    if (rv != SSM_SUCCESS) {
        goto real_loser;
    }

    /* initialize a hash if haven't already */
    if ((*state.hash == NULL) || 
        (SSM_IsAKindOf(target, SSM_RESTYPE_SECADVISOR_CONTEXT)) &&
         ((((SSMSecurityAdvisorContext *)target)->m_certsIncluded & 
          certType[state.usage]) 
        == 0)) {
        ((SSMSecurityAdvisorContext *)target)->m_certsIncluded |= certType[state.usage];
        rv = ssm_populate_key_hash(&state);
        if (rv != SSM_SUCCESS)
            goto user_loser;
    }
        
    /* Already have all certs info, output in a list in state.result */
    SSMSortedList_Enumerate(*state.hash, certlist_display_cert, &state);
    
    if (state.output) {
        rv = SSM_ConcatenateUTF8String(&cx->m_result, state.output);
        /* Finally, add the suffix. */
        rv = SSM_GetAndExpandTextKeyedByString(cx, suffix, &tempUStr);
        if (rv != SSM_SUCCESS)
            goto real_loser; /* error string set by the called function */
        SSM_DebugUTF8String("certlist suffix", tempUStr); 
        
        rv = SSM_ConcatenateUTF8String(&cx->m_result, tempUStr);
        if (rv != SSM_SUCCESS) {
            goto real_loser;
        }
    }
    else /* no certs found */
        {  char * tmpStr = NULL;
        /* release prefix */
        PR_FREEIF(state.output);
        state.output = NULL;
        rv = SSM_GetAndExpandTextKeyedByString(cx, "list_empty", &tmpStr);
        if (rv != SSM_SUCCESS) 
            SSM_DEBUG("SSM_CertListKeywordHandler: could not get filler for empty cert list!\n");
        PR_FREEIF(cx->m_result);
        cx->m_result = NULL;
        rv = SSM_ConcatenateUTF8String(&cx->m_result, tmpStr);
        PR_FREEIF(tmpStr);
        tmpStr = NULL;
        }
    goto done;
user_loser:
    /* If we reach this point, something in the input is wrong, but we
       can still send something back to the client to indicate that a
       problem has occurred. */

    /* If we can't do what we're about to do, really bail. */
    if (!cx->m_request || !cx->m_request->errormsg)
        goto real_loser;

    /* Clear the string we were accumulating. */
    SSMTextGen_UTF8StringClear(&cx->m_result);

    rv = SSM_ConcatenateUTF8String(&cx->m_result, cx->m_request->errormsg);
    /* Clear the result string, since we're sending this inline */
    SSMTextGen_UTF8StringClear(&cx->m_request->errormsg);

    goto done;
real_loser:
    /* If we reach this point, then we are so screwed that we cannot
       send anything vaguely normal back to the client. Bail. */
    if (rv == SSM_SUCCESS) rv = SSM_FAILURE;
done:
    if (targetType && targetType != SSM_RESTYPE_SECADVISOR_CONTEXT)
        if (*state.hash) 
            SSMSortedList_Destroy(*state.hash);    
    PR_FREEIF(state.fmt);
    PR_FREEIF(state.efmt);
    PR_FREEIF(state.output);
    PR_FREEIF(state.temp);
    if (state.datefmt)
        nlsFreeDateFormat(state.datefmt);
    PR_FREEIF(tempUStr);

    return rv;
}

static int ssm_CertsOnSmartcards(SSMControlConnection* ctrl, char* name)
{
    CERTCertList* list = NULL;
    int numCerts = 0;
    
    list = PK11_FindCertsFromNickname(name, (void*)ctrl);
    if (list == NULL) {
        return 0;
    }
    numCerts = SSM_CertListCount(list);
    CERT_DestroyCertList(list);
    return numCerts;
}

static PRBool ssm_OtherCertsByNicknameOrEmailAddr(SSMControlConnection* ctrl,
                                                  char* name)
{
    int numCerts = 0;

    /* first get the number of certs under the nickname */
    numCerts = CERT_NumPermCertsForNickname(ctrl->m_certdb, name);
    if (numCerts == 0) {
        /* we may not see the cert because it's on external token: check it */
        numCerts = ssm_CertsOnSmartcards(ctrl, name);
    }

    if (numCerts > 1) {
        /* note that the cert in question is still in the DB: it is just
         * marked for deletion, but not deleted yet
         */
        return PR_TRUE;
    }
    else if (numCerts == 1) {
        /* it is the only cert */
        return PR_FALSE;
    }
    else {
        /* nickname may be email address: try the email address */
        CERTCertList* list = NULL;
        int count = 0;
        
        list = CERT_CreateEmailAddrCertList(list, ctrl->m_certdb, name, 
                                            PR_Now(), PR_FALSE);
        if (list == NULL) {
            /* this usually shouldn't happen */
            return PR_FALSE;
        }

        count = SSM_CertListCount(list);
        if (list != NULL) {
            CERT_DestroyCertList(list);
        }
        if (count > 1) {
            return PR_TRUE;
        }
        else {
            return PR_FALSE;
        }
    }
}

SSMStatus SSM_ChangeCertSecAdvisorList(HTTPRequest * req, char * nickname, 
				       ssmCertHashAction action)
{
    SSMStatus rv = SSM_SUCCESS;
    SSMControlConnection * ctrl = req->ctrlconn;
    CERTCertificate * dbCert = NULL; 
    PRIntn i;
    SSMSecurityAdvisorContext * advisor = NULL;
    SSMResourceID resID;
    char * tmp;
    SSMSortedList * hash;

    PR_ASSERT(ctrl && SSM_IsAKindOf((SSMResource *)ctrl, 
                                    SSM_RESTYPE_CONTROL_CONNECTION));
    if (!ctrl->m_secAdvisorList || !ctrl->m_secAdvisorList->len) 
        goto done;

    if (action == certHashRemove) /* check if need to delete */ {   
        PR_ASSERT(nickname);

        /* find out if there are other certs under the same nickname than
         * the one marked for deletion
         */
        if (ssm_OtherCertsByNicknameOrEmailAddr(ctrl, nickname)) {
            goto done;
        }
    }
    /* there might be multiple Security Advisor up */
    for (i=0; i<ctrl->m_secAdvisorList->len; i++){
        resID = ctrl->m_secAdvisorList->data[i];
        
        rv = (SSMStatus) SSMControlConnection_GetResource(ctrl, resID, (SSMResource **)&advisor);
        if (rv != SSM_SUCCESS)
            continue;
        PR_ASSERT(advisor && SSM_IsAKindOf((SSMResource *)advisor,
                                           SSM_RESTYPE_SECADVISOR_CONTEXT));
        if (!advisor->m_certhash)
            continue;
        hash = advisor->m_certhash;
        if (action == certHashRemove) 
            SSMSortedList_Remove(hash, nickname, (void **)&tmp);
        else if (action == certHashAdd) {
            ssmCLState state;
            char * form;
            SSMTextGenContext *cx; 

            state.key = clNick;
            state.hash = &advisor->m_certhash;
            state.db = ctrl->m_certdb;
            
            /* this assumes we're called from Restore certificate or 
             * or some other place that includes form name in HTTP request 
             */
            rv = SSM_HTTPParamValue(req, "formName", &form);
            if (strstr(form, "mine")) 
                state.usage = clAllMine;
            else if (strstr(form, "others"))
                state.usage = clEmailRecipient;
            else if (strstr(form, "websites"))
                state.usage = clSSLServer;
            else if (strstr(form, "authorities"))
                state.usage = clAllCA;
            
            rv = SSMTextGen_NewTopLevelContext(req, &cx);
            if (rv != SSM_SUCCESS) {
                SSM_DEBUG("ChangeCertSecAdvisorList: can't get TextGenContext\n");
                goto done;
            }
            state.cx = cx;
            
            rv = ssm_populate_key_hash(&state);
            if (rv != SSM_SUCCESS) 
                SSM_DEBUG("ChangeCertSecAdvisorList: error reading certs\n");
            SSMTextGen_DestroyContext(cx);
        } else
            SSM_DEBUG("ChangeCertSecAdvisorList: bad change request %d\n", action);

        SSM_FreeResource((SSMResource *)advisor);
        advisor = NULL;
    }

done:
    
    if (dbCert) 
        CERT_DestroyCertificate(dbCert);
    return rv;
}
