mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 12:15:51 +00:00
1089 lines
33 KiB
C
1089 lines
33 KiB
C
|
/* -*- 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];
|
||
|
|
||
|
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 = NULL;
|
||
|
|
||
|
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;
|
||
|
}
|