/* -*- 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 "sslconn.h"
#include "ctrlconn.h"
#include "sslerror.h"
#include "serv.h"
#include "servimpl.h"
#include "sslskst.h"
#include "ssmerrs.h"
#include "ssldlgs.h"
#include "collectn.h"
#include "prefs.h"

/* NSS headers */
#include "seccomon.h"
#include "keyt.h"
#include "cert.h"
#include "secasn1.h"
#include "genname.h"

/* Shorthand macros for inherited classes */
#define SSMRESOURCE(sslconn) (&(sslconn)->super.super.super)
#define SSMCONNECTION(sslconn) (&(sslconn)->super.super)
#define SSMDATACONNECTION(sslconn) (&(sslconn)->super)
#define SSMCONTROLCONNECTION(sslconn) ((SSMControlConnection*)(sslconn->super.super.m_parent))

/* SSM_UserCertChoice: enum for cert choice info */
typedef enum {ASK, AUTO} SSM_UserCertChoice;

/* persistent cert choice object we will use here */
static SSM_UserCertChoice certChoice;

/* strings for marking invalid user cert nicknames */
#define NICKNAME_EXPIRED_STRING " (expired)"
#define NICKNAME_NOT_YET_VALID_STRING " (not yet valid)"

/* private function prototypes */
void SSMSSLDataConnection_InitializeUIInfo(SSMSSLUIInfo* info);
void SSMSSLDataConnection_DestroyUIInfo(SSMSSLUIInfo* info);
SSMStatus SSMSSLDataConnection_UpdateSecurityStatus(SSMSSLDataConnection* conn);
SECStatus SSM_SetupSSLSocket(PRFileDesc* socket, PRFileDesc** sslsocket,
                             CERTCertDBHandle* handle, char* hostname,
                             void* wincx);
SSMStatus SSM_GetSSLSocket(SSMSSLDataConnection* conn);
void SSM_SSLDataServiceThread(void* arg);
SSMStatus SSM_SetUserCertChoice(SSMSSLDataConnection* conn);
SECStatus SSM_ConvertCANamesToStrings(PRArenaPool* arena, char** caNameStrings,
                                      CERTDistNames* caNames);
SSMStatus ssm_client_auth_prepare_nicknames(SSMSSLDataConnection* conn,
                                            CERTCertNicknames* nicknames);
PRBool SSM_SSLErrorNeedsDialog(int error);
SECStatus SSM_SSLMakeBadServerCertDialog(int error, 
                                         CERTCertificate* cert,
                                         SSMSSLDataConnection* conn);
SECStatus SSM_SSLVerifyServerCert(CERTCertDBHandle* handle,
                                  CERTCertificate* cert, PRBool checkSig, 
                                  SSMSSLDataConnection* conn);
PRStatus SSMSSLDataConnection_TLSStepUp(SSMSSLDataConnection* conn);
/* callback functions */
SECStatus SSM_SSLAuthCertificate(void* arg, PRFileDesc* socket,
                                 PRBool checkSig, PRBool isServer);
SECStatus SSM_SSLBadCertHandler(void* arg, PRFileDesc* socket);
SECStatus SSM_SSLGetClientAuthData(void* arg,
                                   PRFileDesc* socket,
								   struct CERTDistNamesStr* caNames,
								   struct CERTCertificateStr** pRetCert,
								   struct SECKEYPrivateKeyStr** pRetKey);
void SSM_SSLHandshakeCallback(PRFileDesc* socket, void* clientData);



/* implementations */
/* class functions */
SSMStatus SSMSSLDataConnection_Create(void* arg, 
                                     SSMControlConnection* ctrlconn,
                                     SSMResource** res)
{
    SSMStatus rv = PR_SUCCESS;
    SSMSSLDataConnection* conn;

	/* check arguments */
	if (arg == NULL || res == NULL) {
		goto loser;
	}
	
    *res = NULL; /* in case we fail */
    
    conn = (SSMSSLDataConnection*)PR_CALLOC(sizeof(SSMSSLDataConnection));
    if (!conn) {
		goto loser;
	}

    rv = SSMSSLDataConnection_Init(conn, (SSMInfoSSL*)arg, 
                                   SSM_RESTYPE_SSL_DATA_CONNECTION);
    if (rv != PR_SUCCESS) {
		goto loser;
	}

    SSMSSLDataConnection_Invariant(conn);
    
    *res = &conn->super.super.super;
    return PR_SUCCESS;

loser:
    if (rv == PR_SUCCESS) {
		rv = PR_FAILURE;
	}

    if (conn) {
		SSM_ShutdownResource(SSMRESOURCE(conn), rv);
        SSM_FreeResource(SSMRESOURCE(conn));
    }
        
    return rv;
}

SSMStatus SSMSSLDataConnection_Shutdown(SSMResource* arg, SSMStatus status)
{
    SSMStatus rv;
    SSMSSLDataConnection* conn = (SSMSSLDataConnection*)arg;
	PRBool firstTime = PR_TRUE;
    /* ### sjlee: firstTime doesn't seem to be set anywhere */
    
	/* check argument */
	if (arg == NULL) {
		return PR_FAILURE;
	}
	
    /*SSMSSLDataConnection_Invariant(conn); -- this could have been called from loser */

    SSM_LockResource(arg);

    /* shut down base class */
    rv = SSMDataConnection_Shutdown(arg, status);

	if (firstTime) {
		SSM_DEBUG("First time shutting down SSL connection.\n");
	}

    /* If service threads are done, close the SSL socket */
    if (SSMRESOURCE(conn)->m_threadCount == 0) {
        if (conn->socketSSL) {
			SSM_DEBUG("Closing SSL socket with linger.\n");
            rv = PR_Close(conn->socketSSL);
            conn->socketSSL = NULL;
            SSM_DEBUG("Closed SSL socket (rv == %d).\n", rv);
        }
    }
    SSM_UnlockResource(arg);

	if (!firstTime) {
		rv = (SSMStatus)SSM_ERR_ALREADY_SHUT_DOWN;
	}
    return rv;
}

SSMStatus SSMSSLDataConnection_Init(SSMSSLDataConnection* conn, 
								   SSMInfoSSL* info, SSMResourceType type)
{
    SSMStatus rv = PR_SUCCESS;
    SSMControlConnection* parent;
 
 	/* check arguments */
	if (conn == NULL || info == NULL) {
		goto loser;
	}

    /* fill in the information we got from the request packet */
	parent = info->parent;
    rv = SSMDataConnection_Init(SSMDATACONNECTION(conn), parent, type);
    if (rv != PR_SUCCESS) {
		goto loser;
	}
    conn->isTLS = info->isTLS;
    conn->port = info->port;
    conn->hostIP = info->hostIP;
    conn->hostName = info->hostName;
    /* Initialize UI info */
    SSMSSLDataConnection_InitializeUIInfo(&conn->m_UIInfo);

    if (conn->isTLS) {
        conn->isSecure = PR_FALSE;

        /* Create the step-up FD so that the control connection
           can wake us up */
        conn->stepUpFD = PR_NewPollableEvent();
        if (conn->stepUpFD == NULL)
            goto loser;
    }
    else {
        conn->isSecure = PR_TRUE;
        conn->forceHandshake = info->forceHandshake;

        /* Save the client context */
        SSMRESOURCE(conn)->m_clientContext = info->clientContext;
    }

    /* Spawn the data service thread */
    SSM_DEBUG("Creating SSL data service thread.  Getting ref on SSL "
              "connection.\n");

    /*
     * This reference is for the socket status.  The client needs to tell
     * us when to get rid of it.
     */
    SSM_GetResourceReference(SSMRESOURCE(conn));

    SSMDATACONNECTION(conn)->m_dataServiceThread =
        SSM_CreateThread(SSMRESOURCE(conn), SSM_SSLDataServiceThread);
    if (SSMDATACONNECTION(conn)->m_dataServiceThread == NULL) {
        goto loser;
    }
    conn->m_statusFetched = PR_FALSE;
    return PR_SUCCESS;

loser:
    if (rv == PR_SUCCESS) {
		rv = PR_FAILURE;
	}
    return rv;
}

SSMStatus SSMSSLDataConnection_Destroy(SSMResource* res, PRBool doFree)
{
    SSMSSLDataConnection* conn = (SSMSSLDataConnection*)res;

	if (res == NULL) {
		return PR_FAILURE;
	}
    /* We should be shut down. */
    PR_ASSERT(res->m_threadCount == 0);

    /* Destroy our fields. */
    PR_FREEIF(conn->hostIP);
    PR_FREEIF(conn->hostName);

    /* Destroy UI info. */
    SSMSSLDataConnection_DestroyUIInfo(&conn->m_UIInfo);

    /* Destroy step-up FD if we still have one. */
    if (conn->stepUpFD)
        PR_DestroyPollableEvent(conn->stepUpFD);

    /* Destroy superclass fields. */
    SSMDataConnection_Destroy(SSMRESOURCE(conn), PR_FALSE);

    /* Destroy the socket status if it's there. */
    if (conn->m_sockStat) {
        SSM_FreeResource(&conn->m_sockStat->super);
    }

    /* Free the connection object if asked. */
    if (doFree) {
        PR_DELETE(conn);
	}

    return PR_SUCCESS;
}

void SSMSSLDataConnection_Invariant(SSMSSLDataConnection* conn)
{
    SSMDataConnection_Invariant(SSMDATACONNECTION(conn));

    SSM_LockResource(SSMRESOURCE(conn));
    PR_ASSERT(SSM_IsAKindOf(SSMRESOURCE(conn), 
                            SSM_RESTYPE_SSL_DATA_CONNECTION));
    SSM_UnlockResource(SSMRESOURCE(conn));
}

SSMStatus SSMSSLDataConnection_GetAttrIDs(SSMResource* res,
										 SSMAttributeID** ids, PRIntn* count)
{
    SSMStatus rv;

	if (res == NULL || ids == NULL || count == NULL) {
		goto loser;
	}
    rv = SSMDataConnection_GetAttrIDs(res, ids, count);
    if (rv != PR_SUCCESS) {
        goto loser;
	}

    *ids = (SSMAttributeID *) PR_REALLOC(*ids, (*count + 2)*sizeof(SSMAttributeID));
    if (!*ids) {
		goto loser;
	}

    (*ids)[*count++] = SSM_FID_SSLDATA_SOCKET_STATUS;
    (*ids)[*count++] = SSM_FID_SSLDATA_DISCARD_SOCKET_STATUS;
    goto done;
loser:
    if (rv == PR_SUCCESS) {
		rv = PR_FAILURE;
	}
done:
    return rv;
}

SSMStatus SSMSSLDataConnection_SetAttr(SSMResource * res,
                                       SSMResourceAttrType attrType,
                                       SSMAttributeValue *value)
{
    SSMStatus rv = SSM_FAILURE;
    SSMSSLDataConnection *conn = (SSMSSLDataConnection*)res;

    if (!SSM_IsAKindOf(res, SSM_RESTYPE_SSL_DATA_CONNECTION)) {
        return SSM_FAILURE;
    }
    switch (attrType) {
    case SSM_FID_SSLDATA_DISCARD_SOCKET_STATUS:
        /*
         * Just release the reference we were holding onto.
         */
        if (!conn->m_statusFetched) {
            SSM_FreeResource(res);
            conn->m_statusFetched = PR_TRUE;
        }
        rv = SSM_SUCCESS;
        break;
    default:
        break;
    }
    return rv;
}

SSMStatus SSMSSLDataConnection_GetAttr(SSMResource *res, SSMAttributeID attrID,
                                       SSMResourceAttrType attrType,
                                       SSMAttributeValue *value)
{
    SSMSSLDataConnection* conn = (SSMSSLDataConnection*)res;
    SSMStatus rv = PR_SUCCESS;

	if (res == NULL || value == NULL) {
		goto loser;
	}

    /* see what it is */
    switch(attrID) {
    case SSM_FID_SSLDATA_SOCKET_STATUS:
        /* if the socket status does not exist at this time (which
         * is very unlikely usually), we will wait until the handshake
         * callback creates it
         */
        SSM_LockResource(SSMRESOURCE(conn));
        while (conn->m_sockStat == NULL || 
               conn->m_sockStat->m_cipherName == NULL) {
            SSM_DEBUG("Oops, the security status has not been updated.  Waiting...\n");
            SSM_WaitResource(SSMRESOURCE(conn), PR_INTERVAL_NO_TIMEOUT);
        }
        if (conn->m_sockStat == NULL) {
            SSM_DEBUG("No socket status on dead socket.\n");
            SSM_UnlockResource(SSMRESOURCE(conn));
            goto loser;
        }
        /* We have a socket status object, return its resource ID. */
        value->u.rid = conn->m_sockStat->super.m_id;
        value->type = SSM_RID_ATTRIBUTE;
        SSM_UnlockResource(SSMRESOURCE(conn));

        /*
        if (conn->m_sockStat) {
        }
        else {
            rv = SSM_ERR_ATTRIBUTE_MISSING;
            goto loser;
        }
        */
		break;
    case SSM_FID_SSLDATA_ERROR_VALUE:
        value->type = SSM_NUMERIC_ATTRIBUTE;
        SSM_LockResource(SSMRESOURCE(conn));
        value->u.numeric = conn->m_error;
        SSM_DEBUG("Reported error: %ld\n", conn->m_error);
        SSM_UnlockResource(SSMRESOURCE(conn));
        break;
    default:
        rv = SSMConnection_GetAttr(res, attrID, attrType, value);
        if (rv != PR_SUCCESS) {
			goto loser;
		}
    }

    goto done;
loser:
    value->type = SSM_NO_ATTRIBUTE;
    if (rv == PR_SUCCESS) {
		rv = PR_FAILURE;
	}
done:
    return rv;
}


/*
 * Function: SSMStatus SSMSSLDataConnection_PickleSecurityStatus()
 * Purpose: fills in information on security status (pickled socket status
 *          and the security level) on PickleSecurityStatus request
 *
 * Arguments and return values:
 * - conn: SSL connection object
 * - len: length of the pickled data
 * - blob: pickled data
 * - securityLevel: security level
 * - returns: PR_SUCCESS if successful; error code otherwise
 *
 * Note: Note that this is not really a pickle class function.  This is
 *       specially designed to handle security status requests efficiently.
 */
SSMStatus SSMSSLDataConnection_PickleSecurityStatus(SSMSSLDataConnection* conn,
                                                   PRIntn* len, void** blob,
                                                   PRIntn* securityLevel)
{
    SSMStatus rv = PR_SUCCESS;

    PR_ASSERT(conn != NULL);

    /* in case of failure */
    *len = 0;
    *blob = NULL;
    *securityLevel = 0;

    /* the connection may not be secure yet in case of proxy connections:
     * we should just return and indicate no security if that's the case
     */
    if (!conn->isSecure) {
        return rv;
    }

    /* if the socket status does not exist at this time (which is usually
     * very unlikely), we will wait until the handshake callback creates it
     */
    SSM_LockResource(SSMRESOURCE(conn));
    if (conn->m_sockStat == NULL ||
        conn->m_sockStat->m_cipherName == NULL) {
        SSM_DEBUG("Oops, the security status has not been updated.  "
                  "Waiting...\n");
        SSM_WaitResource(SSMRESOURCE(conn), PR_TicksPerSecond());
    }
    if (conn->m_sockStat == NULL) {
        SSM_DEBUG("No socket status on dead socket.\n");
        SSM_UnlockResource(SSMRESOURCE(conn));
        rv = PR_FAILURE;
        goto loser;
    }
    /* it's probably OK to unlock it here */
    SSM_UnlockResource(SSMRESOURCE(conn));

    /* we have a socket status object. */
    rv = SSMSSLSocketStatus_Pickle(&conn->m_sockStat->super, len, blob);
    if (rv != PR_SUCCESS) {
        SSM_DEBUG("Failed to pickle the socket status.\n");
        goto loser;
    }

    /* get the security level */
    *securityLevel = conn->m_sockStat->m_level;

    /*
     * The client retrieved the socket status, so we can release our
     * refernce now.
     */
    if (!conn->m_statusFetched) {
        SSM_FreeResource(SSMRESOURCE(conn));
        conn->m_statusFetched = PR_TRUE;
    }
 loser:
    return rv;
}


/*
 * Function: SSMStatus SSMSSLDataConnection_FormSubmitHandler()
 * Purpose: handles any UI form submission that has an SSMSSLDataConnection
 *          object as target
 * Expected request:
 * - command: "formsubmit_handler"
 * - baseRef: "formsubmit_dosubmit_js"
 * - target: an SSMSSLDataConnection object
 * - formName: "cert_selection" | "bad_server_cert"
 * Arguments and return values
 * - res: SSMSSLDataConnection object
 * - req: the HTTPRequest object to be processed
 * - returns: SSM_SUCCESS if successful; error otherwise
 *
 * Note: add more handling helper functions if there are other form
 *       submission scenarios
 */
SSMStatus SSMSSLDataConnection_FormSubmitHandler(SSMResource* res,
                                                 HTTPRequest* req)
{
    SSMStatus rv;
    char* tmpStr = NULL;
    SSMSSLDataConnection* conn;


    conn = (SSMSSLDataConnection*)res;
    
    /* make sure you got the right baseRef */
    rv = SSM_HTTPParamValue(req, "baseRef", &tmpStr);
    if (rv != SSM_SUCCESS ||
        PL_strcmp(tmpStr, "windowclose_doclose_js") != 0) {
        goto loser;
    }

    /* differentiate among different form submission events by looking up
     * formName, and dispatch the request to the appropriate handler 
     */
    /* cleaning up (unlocking monitors, etc.) is handled by individual
     * handlers, so just go to done
     */
    if (res->m_formName == NULL) {
        goto loser;
    }
    if (PL_strcmp(res->m_formName, "cert_selection") == 0) {
        rv = SSM_ClientAuthCertSelectionButtonHandler(req);
        goto done;
    }
    else if (PL_strcmp(res->m_formName, "bad_server_cert") == 0) {
        /* server auth failure other than unknown issuer case */
        rv = SSM_ServerAuthFailureButtonHandler(req);
        goto done;
    }
    else {
        /* unknown form name; abort */
        SSM_DEBUG("don't know how to handle this form.\n");
        goto loser;
    }
loser:
    if (rv == SSM_SUCCESS) {
        rv = SSM_FAILURE;
    }
    SSM_LockResource(req->target);
    conn->m_UIInfo.chosen = -1;
    conn->m_UIInfo.trustBadServerCert = BSCA_NO;
    conn->m_UIInfo.UICompleted = PR_TRUE;
    SSM_NotifyResource(req->target);
    SSM_UnlockResource(req->target);
done:
    return rv;
}


void SSMSSLDataConnection_InitializeUIInfo(SSMSSLUIInfo* info)
{
    info->UICompleted = PR_FALSE;
    info->numFilteredCerts = 0;
    info->certNicknames = NULL;
    info->chosen = -1;    /* negative value denotes no cert was chosen */
    info->trustBadServerCert = BSCA_NO;
}

void SSMSSLDataConnection_DestroyUIInfo(SSMSSLUIInfo* info)
{
    int i;

    if (info->numFilteredCerts != 0) {
        for (i = 0; i < info->numFilteredCerts; i++) {
            PR_FREEIF(info->certNicknames[i]);
        }
        PR_FREEIF(info->certNicknames);
    }
}

SSMStatus SSMSSLDataConnection_UpdateSecurityStatus(SSMSSLDataConnection* conn)
{
    SSMStatus rv = PR_SUCCESS;

	PR_ASSERT(conn != NULL);
	
	SSM_LockResource(SSMRESOURCE(conn));
    if (conn->m_sockStat == NULL) {
        /* socket status has not been created.  The security status will
         * be properly populated by creating the socket status.
         */
        SSMResourceID statRID;

        SSM_CreateResource(SSM_RESTYPE_SSL_SOCKET_STATUS, conn->socketSSL, 
                           SSMRESOURCE(conn)->m_connection, &statRID, 
                           (SSMResource**)&conn->m_sockStat);
		conn->m_sockStat->m_error = conn->m_sslServerError;
        /*
         * Just in case someone's waiting on the resource.
         */
        SSM_LockResource(SSMRESOURCE(conn));
        SSM_NotifyResource(SSMRESOURCE(conn));
        SSM_UnlockResource(SSMRESOURCE(conn));
    }
    else {
        /* just update the values */
        rv = SSMSSLSocketStatus_UpdateSecurityStatus(conn->m_sockStat, 
                                                     conn->socketSSL);
        if (rv != PR_SUCCESS) {
            goto loser;
        }
    }
loser:
    /* in case we fail to update the security status, we still notify
     * because we don't want to quit just because this failed...
     */
    SSM_NotifyResource(SSMRESOURCE(conn));
    /* notify SSMSSLDataConnection_GetAttr() for the socket RID */
	SSM_UnlockResource(SSMRESOURCE(conn));

    return rv;
}


/* updates the error code in case of an exception: returns the current
 * error code (0 if no error) 
 */
static 
SSMStatus SSMSSLDataConnection_UpdateErrorCode(SSMSSLDataConnection* conn)
{
    PR_ASSERT(conn != NULL);
    SSM_LockResource(SSMRESOURCE(conn));
    if (conn->m_error == 0) {
        /* update the error code only if it is not already set:
         * otherwise preserve the initial error code
         */
        PRInt32 rv;

        rv = PR_GetError();
        if (rv != SEC_ERROR_LIBRARY_FAILURE) {
            /* XXX sometimes NSS reports this error even when connection
             *     terminates normally: will escape this error here so that
             *     this is not reported erroneously
             */
            conn->m_error = rv;
        }
    }
    SSM_UnlockResource(SSMRESOURCE(conn));

    return conn->m_error;
}

/* functions for setting up and configuring SSL connection */
SECStatus SSM_SetupSSLSocket(PRFileDesc* socket, PRFileDesc** sslsocket,
                             CERTCertDBHandle* handle, char* hostname,
                             void* wincx)
{
    SECStatus rv = SECFailure;

    /* check arguments */
    if ((socket == NULL) || (sslsocket == NULL) || (handle == NULL) || 
        (hostname == NULL) || (wincx == NULL)) {
        goto loser;
    }
    *sslsocket = NULL;

  	/* import the socket into SSL layer */
    *sslsocket = SSL_ImportFD(NULL, socket);
    if (*sslsocket == NULL) {
        goto loser;
    }
  
  	/* set some SSL settings for the socket */
    rv = SSL_Enable(*sslsocket, SSL_SECURITY, PR_TRUE);
    if (rv != SECSuccess) {
		goto loser;
	}

  	rv = SSL_Enable(*sslsocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
    if (rv != SECSuccess) {
		goto loser;
	}

	rv = SSL_Enable(*sslsocket, SSL_ENABLE_FDX, PR_TRUE);
    if (rv != SECSuccess) {
		goto loser;
	}
	
  	/* set callbacks */
  	/* client authentication */
  	rv = SSL_GetClientAuthDataHook(*sslsocket, 
                                   (SSLGetClientAuthData)SSM_SSLGetClientAuthData, 
                                   NULL) == 0 ? SECSuccess : SECFailure;
    if (rv  != SECSuccess) {
		goto loser;
	}
  
  	/* server cert authentication */
	rv = SSL_AuthCertificateHook(*sslsocket,
                                 (SSLAuthCertificate)SSM_SSLAuthCertificate,
                                 (void*)handle) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
		goto loser;
	}
  
  	rv = SSL_BadCertHook(*sslsocket, (SSLBadCertHandler)SSM_SSLBadCertHandler, 
                         wincx) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
  		goto loser;
	}

    rv = SSL_HandshakeCallback(*sslsocket, 
                               (SSLHandshakeCallback)SSM_SSLHandshakeCallback,
                               wincx) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    SSM_DEBUG("setting PKCS11 pin arg.\n");
    rv = SSL_SetPKCS11PinArg(*sslsocket, wincx) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    /* prepare against man-in-the-middle attacks */
    rv = SSL_SetURL(*sslsocket, hostname) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

  	return rv;
loser:
	if (*sslsocket == NULL) {
		/* aborted before SSL_ImportFD() */
		if (socket != NULL) {
			PR_Close(socket);
		}
	}
	else {
		PR_Close(*sslsocket);
        *sslsocket = NULL;
	}

	return rv;
}


SSMStatus SSM_GetSSLSocket(SSMSSLDataConnection* conn)
{
    SECStatus secstatus = SECSuccess;
    PRStatus prstatus = PR_SUCCESS;
    PRFileDesc* socket = NULL;
    PRSocketOptionData sockdata;
    SSMControlConnection* ctrlconn;
    PRNetAddr addr;
    PRHostEnt hostentry;
    char buffer[256];

    SSM_DEBUG("SSM_GetSSLSocket entered.\n");

	/* check argument first */
	if (conn == NULL) {
		return PR_FAILURE;
	}
	
    /* Enter SSL lock. We will release once connection is set up. 
     * If GetSecurityStatus request comes in before we are done, it'll spin 
     * on the SSL lock. (This lock is not used anyplace else.)
     */
  
    /*PR_EnterMonitor(conn->sslLock);*/

    /* set up SSL secure socket */
    SSM_DEBUG("setting up secure socket.\n");
    socket = PR_NewTCPSocket();
    if (socket == NULL) {
        goto loser;
    }

    /* make the socket blocking - default on some platforms is non-blocking */
    sockdata.option = PR_SockOpt_Nonblocking;
    sockdata.value.non_blocking = PR_FALSE;
    if (PR_SetSocketOption(socket, &sockdata) != PR_SUCCESS) {
        goto loser;
    }


    if (!conn->isTLS) {
        /* set up SSL secure socket */
        SSM_DEBUG("setting up secure socket.\n");

        ctrlconn = SSMCONTROLCONNECTION(conn);
        secstatus = SSM_SetupSSLSocket(socket, &conn->socketSSL,
                                       ctrlconn->m_certdb, conn->hostName, 
                                       (void*)conn);
        if (secstatus != SECSuccess) {
            goto loser;
        }
    }
    else {
        /* do not need to create a secure socket here */
        conn->socketSSL = socket;
    }
	
    /* prepare and setup network connection */
    SSM_DEBUG("preparing and setting up network connection.\n");
    if (PR_StringToNetAddr(conn->hostIP, &addr) != PR_SUCCESS) {
        PRIntn hostIndex;

        prstatus = PR_GetHostByName(conn->hostName, buffer, 256, &hostentry);
	if (prstatus != PR_SUCCESS) {
	    goto loser;
	}

  
	SSM_DEBUG("SSL server port: %d / host: %s \n", conn->port, 
		  conn->hostName);
	do {
	    hostIndex = PR_EnumerateHostEnt(0, &hostentry, 
					    (PRUint16) conn->port, &addr);
	    if (hostIndex < 0) {
	        goto loser;
	    }
	} while (PR_Connect(conn->socketSSL, &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS
		 && hostIndex > 0);

	SSM_DEBUG("Connected to target.\n");
    } else {
        if (PR_InitializeNetAddr(PR_IpAddrNull, (PRUint16)conn->port, &addr) != PR_SUCCESS) {
	    goto loser;
	}
	if (PR_Connect(conn->socketSSL, &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS) {
	  goto loser;
	}
	SSM_DEBUG("SSL server port: %d / host: %s\n", conn->port, 
		  conn->hostName);
	SSM_DEBUG("connected using hostIP: %s\n", conn->hostIP);
    }

#ifdef _HACK_
    /* ### mwelch Fix for NSPR20 connect problem under 95/NT. */
    PR_Sleep(PR_INTERVAL_NO_WAIT);
#endif

    /* established SSL connection, ready to send data */
    if (!conn->isTLS && conn->forceHandshake) {
        /* if the client wants a forced handshake (e.g. SSL/IMAP), 
         * do it here 
         */
        SSM_DEBUG("forcing handshake.\n");
        secstatus = (SSL_ForceHandshake(conn->socketSSL) == 0)? SECSuccess:SECFailure;
        if (secstatus != SECSuccess) {
            goto loser;
        }
    }

    /*
	if (conn->m_sockStat) {
        SSM_FreeResource(&conn->m_sockStat->super);
	}
    SSM_CreateResource(SSM_RESTYPE_SSL_SOCKET_STATUS, conn->socketSSL, 
                       SSMRESOURCE(conn)->m_connection, &statRID, 
                       (SSMResource**)&conn->m_sockStat);
    */	
	/* check everything is hunky-dorey */
    /*	SSMSSLSocketStatus_Invariant(conn->m_sockStat); */
    /* end of the block */

    /* release SSL lock. SSL connection is established. */
    /*PR_Notify(conn->sslLock);
      PR_ExitMonitor(conn->sslLock);*/

    SSM_DEBUG("returning OK.\n");

    return prstatus;

loser:
    (void)SSMSSLDataConnection_UpdateErrorCode(conn);
    SSM_LockResource(SSMRESOURCE(conn));
    SSM_DEBUG("error %d (%s), exiting abnormally.\n", conn->m_error, 
              SSL_Strerror(conn->m_error));
    SSM_UnlockResource(SSMRESOURCE(conn));
    return PR_FAILURE;
}


/* 
 * Function: PRStatus SSMSSLDataConnection_TLSStepUp()
 * Purpose: "step up" the connection and set up an SSL socket for the 
 *          underlying TCP socket
 * Arguments and return values:
 * - conn: TLS connection object to be manipulated
 * - returns: PR_SUCCESS if successful; PR_FAILURE otherwise
 *
 * Notes: SSL handshakes are not done here; it is performed when the first
 *        read/write operation is done on the secure socket
 *        This function must be called while the underlying resource is
 *        locked
 */
static PRStatus SSMSSLDataConnection_TLSStepUp(SSMSSLDataConnection* conn)
{
    PRFileDesc* socket = conn->socketSSL;
    PRFileDesc* sslsocket = NULL;

    PR_ASSERT(conn->isTLS == PR_TRUE);

    if (conn->isSecure == PR_TRUE) {
        /* socket is already secure */
        return PR_SUCCESS;
    }

    SSM_DEBUG("Setting up secure socket for this TLS connection.\n");
    if (SSM_SetupSSLSocket(socket, &sslsocket,
                           SSMCONTROLCONNECTION(conn)->m_certdb, 
                           conn->hostName, (void*)conn) != SECSuccess) {
        goto loser;
    }

    SSM_DEBUG("Resetting handshake.\n");
    if (SSL_ResetHandshake(sslsocket, PR_FALSE) != SECSuccess) {
        goto loser;
    }

    /* XXX it is important that the SSL handshake does not take place here
     *     because that would create a deadlock on this thread
     */
    SSM_DEBUG("We now have a secure socket.\n");
    /* secure socket is established */
    conn->socketSSL = sslsocket;
    conn->isSecure = PR_TRUE;

    return PR_SUCCESS;
loser:
    (void)SSMSSLDataConnection_UpdateErrorCode(conn);
    if (sslsocket == NULL) {
        if (socket != NULL) {
            PR_Close(socket);
        }
    }
    else {
        PR_Close(sslsocket);
    }

    return PR_FAILURE;
}

/* thread function */
/* SSL connection is serviced by the data service thread that works on
 * the client data socket and the SSL socket.
 * 
 * Our poll descriptor array contains the client data socket and the SSL 
 * target socket in this order.
 */
#define SSL_PDS 3
#define READSSL_CHUNKSIZE 16384

void SSM_SSLDataServiceThread(void* arg)
{
    SSMStatus rv = PR_SUCCESS;
    SSMSSLDataConnection* conn = NULL;
    PRPollDesc pds[SSL_PDS];
    PRIntn nSockets = 0;
    PRInt32 nReady;
    int i;
    PRIntn read = 0;
    PRIntn sent = 0;
    char *outbound = NULL;
    char *inbound = NULL;
    PRIntn oBufSize;
    
    SSM_RegisterNewThread("ssl data", (SSMResource *) arg);
    conn = (SSMSSLDataConnection*)arg;
    SSM_DEBUG("initializing.\n");
    if (arg == NULL) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        goto loser;
    }

    SSMDATACONNECTION(conn)->m_dataServiceThread = PR_GetCurrentThread();

	/* initialize xfer buffers */
	outbound = (char *) PR_CALLOC(READSSL_CHUNKSIZE+1);
	if (outbound == NULL)
		goto loser;

	inbound = (char *) PR_CALLOC(READSSL_CHUNKSIZE+1);
	if (inbound == NULL)
		goto loser;

    /* initialize the poll descriptors */
    for (i = 0; i < SSL_PDS; i++) {
        pds[i].fd = NULL;
    }

    /* set up sockets */
    /* set up the client data socket */
    rv = SSMDataConnection_SetupClientSocket(SSMDATACONNECTION(conn));
    if (rv != PR_SUCCESS) {
        goto loser;
    }

    /* save the client data socket in the array */
    pds[0].fd = SSMDATACONNECTION(conn)->m_clientSocket;
    pds[0].in_flags = PR_POLL_READ;
    nSockets++;

    /* set up the SSL socket */
    rv = SSM_GetSSLSocket(conn);
    if (rv != PR_SUCCESS) {
        goto loser;
    }

    /* save the SSL socket in the array */
    pds[1].fd = conn->socketSSL;
    pds[1].in_flags = PR_POLL_READ;
    nSockets++;

    if (conn->isTLS) {
        /* SSL/SMTP connections usually require a large outbound buffer */
        oBufSize = READSSL_CHUNKSIZE;

        /* Add step-up FD to the list of FDs until we've been asked to
           encrypt */
        pds[2].fd = conn->stepUpFD;
        pds[2].in_flags = PR_POLL_READ;
        nSockets++;
    }
    else {
        oBufSize = LINESIZE;
    }

    /* spinning mode.  Exchange data between the client socket and the SSL
     * socket.
     */
    while ((nSockets > 0) && 
           (SSM_Count(SSMDATACONNECTION(conn)->m_shutdownQ) == 0)) {
        /* XXX Nova-specific: Nova does not handle active close from the
         *     server properly in case of keep-alive connections: what
         *     happens is that although we perform a shutdown on the client
         *     socket, Nova never sends us a close.  If the SSL socket is 
         *     removed, we might as well close the client socket by hand.
         */
        /* if the outbound connection is shut down and there is no data
         * pending in the client socket, perhaps the client is acting up
         */
        if (pds[1].fd == NULL) {
            /* don't block in polling */
            nReady = PR_Poll(pds, SSL_PDS, PR_INTERVAL_NO_WAIT);
            if (nReady <= 0) {
                /* either error or the client socket is blocked.  Bail. */
                rv = SSMSSLDataConnection_UpdateErrorCode(conn);
                if (SSMDATACONNECTION(conn)->m_clientSocket != NULL) {
                    PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket,
                                PR_SHUTDOWN_SEND);
                }
                nSockets--;
                if (nSockets > 1)
                    {
                        /* 
                           We were waiting for notification to step up,
                           but that notification won't happen. So,
                           remove stepUpFD from the list as well.
                        */
                        pds[2].fd = NULL;
                        nSockets = 1;
                    }
                break;    /* go to loser; */
            }
        }

        SSM_DEBUG("Polling sockets for pending data.\n");
        /* poll sockets for pending data */
#if 0
        if (conn->isTLS == PR_TRUE) {
            /* we require the lock for this operation and do a non-blocking
             * poll in case of TLS because we want to yield to the step-up 
             * operation
             */
            SSM_LockResource(SSMRESOURCE(conn));
            nReady = PR_Poll(pds, SSL_PDS, PR_INTERVAL_NO_WAIT);
            SSM_UnlockResource(SSMRESOURCE(conn));
            if (nReady == 0) {
                continue;
            }
        }
        else {
#endif
            /* Poll for however many sockets we're interested in. */
            nReady = PR_Poll(pds, nSockets, PR_INTERVAL_NO_TIMEOUT);
#if 0
        }
#endif
        if (nReady < 0) {
            rv = SSMSSLDataConnection_UpdateErrorCode(conn);
            goto loser;
        }

        /* Wind down from SSL_PDS because the pollable event
           takes priority over the I/O sockets. */
        for (i = (SSL_PDS-1); i >= 0; i--) {
            if (pds[i].fd == NULL) {
                continue;
            }

            /* check sockets for data */
            if (pds[i].out_flags & (PR_POLL_READ | PR_POLL_EXCEPT)) {
                if (pds[i].fd == SSMDATACONNECTION(conn)->m_clientSocket) {
                    /* got data at the client socket */
                    SSM_DEBUG("Attempting to read %ld bytes from client "
                              "socket.\n", oBufSize);
                    read = PR_Recv(SSMDATACONNECTION(conn)->m_clientSocket,
                                   outbound, oBufSize, 0,
                                   PR_INTERVAL_NO_TIMEOUT);
                    
                    if (read <= 0) {
                        if (read < 0) {
                            /* Got an error */
                            rv = SSMSSLDataConnection_UpdateErrorCode(conn);
                            SSM_DEBUG("Error receiving data over client "
                                      "socket, status == %ld\n", (long)rv);
                        }
                        else {
                            /* We got an EOF */
                            SSM_DEBUG("Got EOF at client socket.\n");
                            rv = PR_SUCCESS;
                        }

                        if (conn->socketSSL != NULL) {
                            SSM_DEBUG("Shutting down the target socket.\n");
                            PR_Shutdown(conn->socketSSL, PR_SHUTDOWN_SEND);
                        }

                        /* remove the socket from the array */
                        pds[i].fd = NULL;
                        nSockets--;
                    }
                    else {
                        SSM_DEBUG("data: <%s>\n" "Send the data to the "
                                  "target.\n", outbound);
                        sent = PR_Send(conn->socketSSL, outbound, read, 0,
                                       PR_INTERVAL_NO_TIMEOUT);
                        if (sent != read) {
                        	SSM_DEBUG("Couldn't send all data to the target socket (sent: %d read: %d)\n",
                        		sent, read);
                            rv = SSMSSLDataConnection_UpdateErrorCode(conn);
                            
                            if (SSMDATACONNECTION(conn)->m_clientSocket != 
				NULL) {
                                SSM_DEBUG("Shutting down the client socket.\n");
                                PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket, 
					    PR_SHUTDOWN_SEND);
                            }

                            /* remove the socket from the array */
                            pds[i].fd = NULL;
                            nSockets--;
                        }
                    }
                }
                else if (pds[i].fd == conn->socketSSL) {
                    /* got data at the SSL socket */
                    SSM_DEBUG("Reading data from target socket.\n");
                    read = PR_Recv(conn->socketSSL, inbound, READSSL_CHUNKSIZE,
                                   0, PR_INTERVAL_NO_TIMEOUT);

                    if (read <= 0) {
                        if (read < 0) {
                            /* Got error */
                            rv = SSMSSLDataConnection_UpdateErrorCode(conn);
                            SSM_DEBUG("Error receiving data from target "
                                      "socket, status = %ld\n", rv);
                        }
                        else {
                            /* We got an EOF */
                            SSM_DEBUG("Got EOF at target socket.\n");
                            rv = PR_SUCCESS;
                        }

                        if (SSMDATACONNECTION(conn)->m_clientSocket != NULL) {
			    SSM_DEBUG("Shutting down the client socket.\n");
                            PR_Shutdown(SSMDATACONNECTION(conn)->m_clientSocket, 
                                        PR_SHUTDOWN_SEND);
                        }

                        /* remove the socket from the array */
                        pds[i].fd = NULL;
                        nSockets--;
                    }
                    else {
                        /* Got data, write it to the client socket */
                        SSM_DEBUG("Writing to client socket.\n");
#if 0
                        SSM_DumpBuffer(inbound, read);
#endif
                        sent = PR_Send(SSMDATACONNECTION(conn)->m_clientSocket,
                                       inbound, read, 0, 
                                       PR_INTERVAL_NO_TIMEOUT);
                        if (sent != read) {
                            rv = SSMSSLDataConnection_UpdateErrorCode(conn);

                            if (conn->socketSSL != NULL) {
			        SSM_DEBUG("Shutting down the target socket.\n");
                                PR_Shutdown(conn->socketSSL, PR_SHUTDOWN_SEND);
                            }

                            /* remove the socket from the array */
                            pds[i].fd = NULL;
                            nSockets--;
                        }
                        SSM_DEBUG("Wrote %ld bytes.\n", sent);
                    }
                }
                else if (pds[i].fd == conn->stepUpFD)
                {
                    /* We've been told to enable TLS on this connection.
                       Clear the event, step up, and reconfigure sockets. */

                    PRFileDesc *stepUpFD = conn->stepUpFD;

                    /* Make sure the control connection has settled */
                    SSM_LockResource(SSMRESOURCE(conn));
                    if ((pds[i].out_flags & PR_POLL_READ) != 0)
                        /* it's readable, so it should return immediately */
                        PR_WaitForPollableEvent(stepUpFD);

                    /* Step up to encryption. */
                    rv = SSMSSLDataConnection_TLSStepUp(conn);
                    conn->m_error = rv; /* tell the control connection */

                    /* Remove the step-up fd */
                    conn->stepUpFD = NULL;
                    pds[i].fd = NULL;
                    nSockets--;
                    PR_DestroyPollableEvent(stepUpFD);

                    /* Notify ourselves to wake up the control connection */
                    SSM_NotifyResource(SSMRESOURCE(conn));
                    SSM_UnlockResource(SSMRESOURCE(conn));
                }
            }
        }    /* end of for loop */
    }    /* end of while loop */
loser:
    SSM_DEBUG("** Closing, return value %ld. **\n", rv);
    if (conn != NULL) {
      	SSM_ShutdownResource(SSMRESOURCE(conn), rv);
        SSM_FreeResource(SSMRESOURCE(conn));
    }
    if (outbound != NULL) {
        PR_Free(outbound);
    }
    if (inbound != NULL) {
        PR_Free(inbound);
    }
    SSM_DEBUG("SSL data service thread terminated.\n");
}


/* callback functions and auxilliary functions */
/*
 * Function: SECStatus SSM_SSLAuthCertificate()
 * Purpose: this callback function is used to authenticate certificates
 *			received from the remote end of an SSL connection.
 *
 * Arguments and return values
 * - arg: certDB handle (cannot be NULL)
 * - socket: SSL socket
 * - checkSig: whether to check the signature (usually should be PR_TRUE)
 * - isServer: whether we are an SSL server (expect PR_FALSE since Cartman is
 *			   an SSL client)
 * - returns: SECSuccess if successful; error code otherwise
 *
 * ### sjlee: this is essentially a wrapper over the default SSL callback
 *			  function.  It provides minimal argument checking to the default
 *			  callback.  When we're confident that the default will do the
 *			  job, we might want to drop this...
 */
SECStatus SSM_SSLAuthCertificate(void* arg, PRFileDesc* socket, 
								 PRBool checkSig, PRBool isServer)
{
	SSM_DEBUG("checking server cert.\n");

	/* check arguments */
	if (arg == NULL || socket == NULL || isServer) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return SECFailure;
	}

	/* call the default callback */
	return (SECStatus) SSL_AuthCertificate(arg, socket, checkSig, isServer);
}


/*
 * Function: SECStatus SSM_SSLBadCertHandler()
 * Purpose: this callback function is used to handle a situation in which 
 *			server certificate verification has failed.
 *
 * Arguments and return values
 * - arg: the SSL data connection
 * - socket: SSL socket
 * - returns: SECSuccess if the case is handled and it is authorized to
 *			  continue connection; otherwise SECFailure
 */
SECStatus SSM_SSLBadCertHandler(void* arg, PRFileDesc* socket)
{
	int error;
    SSMSSLDataConnection* conn;
    CERTCertificate* cert = NULL;
    SECStatus rv = SECFailure;
	
    if (socket == NULL || arg == NULL) {
        return SECFailure;
    }
    conn = (SSMSSLDataConnection*)arg;

    if (!(SSMCONTROLCONNECTION(conn)->m_doesUI)) {
        /* UI-less application.  We choose to reject the server cert. */
        goto loser;
    }

    cert = SSL_PeerCertificate(conn->socketSSL);
    if (cert == NULL) {
        goto loser;
    }

    while (rv != SECSuccess) {
        error = PR_GetError();	/* first of all, get error code */
	
        SSM_DEBUG("Got a bad server cert: error %d (%s).\n", error, 
                  SSL_Strerror(error));
	
		/* Save error for socket status */
		conn->m_sslServerError = error;

        if (!SSM_SSLErrorNeedsDialog(error)) {
            SSM_DEBUG("Exiting abnormally...\n");
            break;
        }

        if (SSM_SSLMakeBadServerCertDialog(error, cert, conn) != SECSuccess) {
            break;
        }

        /* check the server cert again for more errors */
        rv = SSM_DoubleCheckServerCert(cert, conn);
    }

loser:
    if (rv != SECSuccess) {
        (void)SSMSSLDataConnection_UpdateErrorCode(conn);
    }

    if (cert != NULL) {
        CERT_DestroyCertificate(cert);
    }
    return rv;
}

/*
 * structs and ASN1 templates for the limited scope-of-use extension
 *
 * CertificateScopeEntry ::= SEQUENCE {
 *     name GeneralName, -- pattern, as for NameConstraints
 *     portNumber INTEGER OPTIONAL }
 *
 * CertificateScopeOfUse ::= SEQUENCE OF CertificateScopeEntry
 */
/*
 * CERTCertificateScopeEntry: struct for scope entry that can be consumed by
 *                            the code
 * certCertificateScopeOfUse: struct that represents the decoded extension data
 */
typedef struct {
    SECItem derConstraint;
    SECItem derPort;
    CERTGeneralName* constraint; /* decoded constraint */
    PRIntn port; /* decoded port number */
} CERTCertificateScopeEntry;

typedef struct {
    CERTCertificateScopeEntry** entries;
} certCertificateScopeOfUse;

/* corresponding ASN1 templates */
static const SEC_ASN1Template cert_CertificateScopeEntryTemplate[] = {
    { SEC_ASN1_SEQUENCE, 
      0, NULL, sizeof(CERTCertificateScopeEntry) },
    { SEC_ASN1_ANY,
      offsetof(CERTCertificateScopeEntry, derConstraint) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_INTEGER,
      offsetof(CERTCertificateScopeEntry, derPort) },
    { 0 }
};

static const SEC_ASN1Template cert_CertificateScopeOfUseTemplate[] = {
    { SEC_ASN1_SEQUENCE_OF, 0, cert_CertificateScopeEntryTemplate }
};

/* 
 * decodes the extension data and create CERTCertificateScopeEntry that can
 * be consumed by the code
 */
static
SECStatus cert_DecodeScopeOfUseEntries(PRArenaPool* arena, SECItem* extData,
                                       CERTCertificateScopeEntry*** entries,
                                       int* numEntries)
{
    certCertificateScopeOfUse* scope = NULL;
    SECStatus rv = SECSuccess;
    int i;

    *entries = NULL; /* in case of failure */
    *numEntries = 0; /* ditto */

    scope = (certCertificateScopeOfUse*)
        PORT_ArenaZAlloc(arena, sizeof(certCertificateScopeOfUse));
    if (scope == NULL) {
        goto loser;
    }

    rv = SEC_ASN1DecodeItem(arena, (void*)scope, 
                            cert_CertificateScopeOfUseTemplate, extData);
    if (rv != SECSuccess) {
        goto loser;
    }

    *entries = scope->entries;
    PR_ASSERT(*entries != NULL);

    /* first, let's count 'em. */
    for (i = 0; (*entries)[i] != NULL; i++) ;
    *numEntries = i;

    /* convert certCertificateScopeEntry sequence into what we can readily
     * use
     */
    for (i = 0; i < *numEntries; i++) {
        (*entries)[i]->constraint = 
            cert_DecodeGeneralName(arena, &((*entries)[i]->derConstraint), 
                                   NULL);
        if ((*entries)[i]->derPort.data != NULL) {
            (*entries)[i]->port = 
                (int)DER_GetInteger(&((*entries)[i]->derPort));
        }
        else {
            (*entries)[i]->port = 0;
        }
    }

    goto done;
loser:
    if (rv == SECSuccess) {
        rv = SECFailure;
    }
done:
    return rv;
}

static SECStatus cert_DecodeCertIPAddress(SECItem* genname, 
                                          PRUint32* constraint, PRUint32* mask)
{
    /* in case of failure */
    *constraint = 0;
    *mask = 0;

    PR_ASSERT(genname->data != NULL);
    if (genname->data == NULL) {
        return SECFailure;
    }
    if (genname->len != 8) {
        /* the length must be 4 byte IP address with 4 byte subnet mask */
        return SECFailure;
    }

    /* get them in the right order */
    *constraint = PR_ntohl((PRUint32)(*genname->data));
    *mask = PR_ntohl((PRUint32)(*(genname->data + 4)));

    return SECSuccess;
}

static char* _str_to_lower(char* string)
{
#ifdef XP_WIN
    return _strlwr(string);
#else
    int i;
    for (i = 0; string[i] != '\0'; i++) {
        string[i] = tolower(string[i]);
    }
    return string;
#endif
}

/*
 * Sees if the client certificate has a restriction in presenting the cert
 * to the host: returns PR_TRUE if there is no restriction or if the hostname
 * (and the port) satisfies the restriction, or PR_FALSE if the hostname (and
 * the port) does not satisfy the restriction
 */
static PRBool CERT_MatchesScopeOfUse(CERTCertificate* cert, char* hostname,
                                     char* hostIP, PRIntn port)
{
    PRBool rv = PR_TRUE; /* whether the cert can be presented */
    SECStatus srv;
    SECItem extData;
    PRArenaPool* arena = NULL;
    CERTCertificateScopeEntry** entries = NULL;
    /* arrays of decoded scope entries */
    int numEntries = 0;
    int i;
    char* hostLower = NULL;
    PRUint32 hostIPAddr = 0;

    PR_ASSERT((cert != NULL) && (hostname != NULL) && (hostIP != NULL));

    /* find cert extension */
    srv = CERT_FindCertExtension(cert, SEC_OID_NS_CERT_EXT_SCOPE_OF_USE,
                                 &extData);
    if (srv != SECSuccess) {
        /* most of the time, this means the extension was not found: also,
         * since this is not a critical extension (as of now) we may simply
         * return PR_TRUE
         */
        goto done;
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto done;
    }

    /* decode the scope of use entries into pairs of GeneralNames and
     * an optional port numbers
     */
    srv = cert_DecodeScopeOfUseEntries(arena, &extData, &entries, &numEntries);
    if (srv != SECSuccess) {
        /* XXX What should we do when we failed to decode the extension?  This
         *     may mean either the extension was malformed or some (unlikely)
         *     fatal error on our part: my argument is that if the extension 
         *     was malformed the extension "disqualifies" as a valid 
         *     constraint and we may present the cert
         */
        goto done;
    }

    /* loop over these structures */
    for (i = 0; i < numEntries; i++) {
        /* determine whether the GeneralName is a DNS pattern, an IP address 
         * constraint, or else
         */
        CERTGeneralName* genname = entries[i]->constraint;

        /* if constraint is NULL, don't bother looking */
        if (genname == NULL) {
            /* this is not a failure: just continue */
            continue;
        }

        switch (genname->type) {
        case certDNSName: {
            /* we have a DNS name constraint; we should use only the host name
             * information
             */
            char* pattern = NULL;
            char* substring = NULL;

            /* null-terminate the string */
            genname->name.other.data[genname->name.other.len] = '\0';
            pattern = _str_to_lower(genname->name.other.data);

            if (hostLower == NULL) {
                /* so that it's done only if necessary and only once */
                hostLower = _str_to_lower(PL_strdup(hostname));
            }

            /* the hostname satisfies the constraint */
            if (((substring = strstr(hostLower, pattern)) != NULL) &&
                /* the hostname contains the pattern */
                (strlen(substring) == strlen(pattern)) &&
                /* the hostname ends with the pattern */
                ((substring == hostLower) || (*(substring-1) == '.'))) {
                /* the hostname either is identical to the pattern or
                 * belongs to a subdomain
                 */
                rv = PR_TRUE;
            }
            else {
                rv = PR_FALSE;
            }
            /* clean up strings if necessary */
            break;
        }
        case certIPAddress: {
            PRUint32 constraint;
            PRUint32 mask;
            PRNetAddr addr;
            
            if (hostIPAddr == 0) {
                /* so that it's done only if necessary and only once */
                PR_StringToNetAddr(hostIP, &addr);
                hostIPAddr = addr.inet.ip;
            }

            if (cert_DecodeCertIPAddress(&(genname->name.other), &constraint, 
                                         &mask) != SECSuccess) {
                continue;
            }
            if ((hostIPAddr & mask) == (constraint & mask)) {
                rv = PR_TRUE;
            }
            else {
                rv = PR_FALSE;
            }
            break;
        }
        default:
            /* ill-formed entry: abort */
            continue; /* go to the next entry */
        }

        if (!rv) {
            /* we do not need to check the port: go to the next entry */
            continue;
        }

        /* finally, check the optional port number */
        if ((entries[i]->port != 0) && (port != entries[i]->port)) {
            /* port number does not match */
            rv = PR_FALSE;
            continue;
        }

        /* we have a match */
        PR_ASSERT(rv);
        break;
    }
done:
    /* clean up entries */
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    if (hostLower != NULL) {
        PR_Free(hostLower);
    }
    return rv;
}

/*
 * Function: SECStatus SSM_SSLGetClientAuthData()
 * Purpose: this callback function is used to pull client certificate
 *			information upon server request
 *
 * Arguments and return values
 * - arg: SSL data connection
 * - socket: SSL socket we're dealing with
 * - caNames: list of CA names
 * - pRetCert: returns a pointer to a pointer to a valid certificate if
 *			   successful; otherwise NULL
 * - pRetKey: returns a pointer to a pointer to the corresponding key if
 *			  successful; otherwise NULL
 * - returns: SECSuccess if successful; error code otherwise
 */
SECStatus SSM_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
								   CERTDistNames* caNames,
								   CERTCertificate** pRetCert,
								   SECKEYPrivateKey** pRetKey)
{
	void* wincx = NULL;
	SECStatus rv = SECFailure;
	SSMSSLDataConnection* conn;
	SSMControlConnection* ctrlconn;
    PRArenaPool* arena = NULL;
    char** caNameStrings = NULL;
    CERTCertificate* cert = NULL;
    SECKEYPrivateKey* privKey = NULL;
    CERTCertList* certList = NULL;
    CERTCertListNode* node;
    CERTCertNicknames* nicknames = NULL;
    char* extracted = NULL;
    PRIntn keyError = 0; /* used for private key retrieval error */
	
	SSM_DEBUG("Client authentication callback function called.\n");
	
	/* do some argument checking */
	if (socket == NULL || caNames == NULL || pRetCert == NULL ||
		pRetKey == NULL) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return SECFailure;
	}
	
	/* get PKCS11 pin argument */
	wincx = SSL_RevealPinArg(socket);
	if (wincx == NULL) {
		return SECFailure;
	}
	
	/* get the right connections */
	conn = (SSMSSLDataConnection*)wincx;
	ctrlconn = (SSMControlConnection*)(SSMCONNECTION(conn)->m_parent);
	PR_ASSERT(ctrlconn);

    /* create caNameStrings */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    caNameStrings = (char**)PORT_ArenaAlloc(arena, 
                                            sizeof(char*)*(caNames->nnames));
    if (caNameStrings == NULL) {
        goto loser;
    }

    rv = SSM_ConvertCANamesToStrings(arena, caNameStrings, caNames);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* get the preference */
	if (SSM_SetUserCertChoice(conn) != SSM_SUCCESS) {
		goto loser;
	}

	/* find valid user cert and key pair */	
	if (certChoice == AUTO) {
		/* automatically find the right cert */

        /* find all user certs that are valid and for SSL */
        certList = CERT_FindUserCertsByUsage(ctrlconn->m_certdb, 
                                             certUsageSSLClient, PR_TRUE,
                                             PR_TRUE, wincx);
        if (certList == NULL) {
            goto noCert;
        }

        /* filter the list to those issued by CAs supported by the server */
        rv = CERT_FilterCertListByCANames(certList, caNames->nnames,
                                          caNameStrings, certUsageSSLClient);
        if (rv != SECSuccess) {
            goto noCert;
        }

        /* make sure the list is not empty */
        node = CERT_LIST_HEAD(certList);
        if (CERT_LIST_END(node, certList)) {
            goto noCert;
        }

        /* loop through the list until we find a cert with a key */
        while (!CERT_LIST_END(node, certList)) {
            /* if the certificate has restriction and we do not satisfy it
             * we do not use it
             */
            if (!CERT_MatchesScopeOfUse(node->cert, conn->hostName,
                                        conn->hostIP, conn->port)) {
                node = CERT_LIST_NEXT(node);
                continue;
            }

            privKey = PK11_FindKeyByAnyCert(node->cert, wincx);
            if (privKey != NULL) {
                /* this is a good cert to present */
                cert = CERT_DupCertificate(node->cert);
                break;
            }
            keyError = PR_GetError();
            if (keyError == SEC_ERROR_BAD_PASSWORD) {
                /* problem with password: bail */
                goto loser;
            }

            node = CERT_LIST_NEXT(node);
        }
        if (cert == NULL) {
            goto noCert;
        }
	}
	else {
        /* user selects a cert to present */
        int i;

        /* find all user certs that are valid and for SSL */
        /* note that we are allowing expired certs in this list */
        certList = CERT_FindUserCertsByUsage(ctrlconn->m_certdb, 
                                             certUsageSSLClient, PR_TRUE, 
                                             PR_FALSE, wincx);
        if (certList == NULL) {
            goto noCert;
        }

        if (caNames->nnames != 0) {
            /* filter the list to those issued by CAs supported by the 
             * server 
             */
            rv = CERT_FilterCertListByCANames(certList, caNames->nnames, 
                                              caNameStrings, 
                                              certUsageSSLClient);
            if (rv != SECSuccess) {
                goto loser;
            }
        }

        if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
            /* list is empty - no matching certs */
            goto noCert;
        }

        /* filter it further for hostname restriction */
        node = CERT_LIST_HEAD(certList);
        while (!CERT_LIST_END(node, certList)) {
            if (!CERT_MatchesScopeOfUse(node->cert, conn->hostName,
                                        conn->hostIP, conn->port)) {
                CERTCertListNode* removed = node;
                node = CERT_LIST_NEXT(removed);
                CERT_RemoveCertListNode(removed);
            }
            else {
                node = CERT_LIST_NEXT(node);
            }
        }
        if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
            goto noCert;
        }

        nicknames = CERT_NicknameStringsFromCertList(certList,
                                                     NICKNAME_EXPIRED_STRING,
                                                     NICKNAME_NOT_YET_VALID_STRING);
        if (nicknames == NULL) {
            goto loser;
        }

        SSM_DEBUG("%d valid user certs found.\n", nicknames->numnicknames);
        conn->m_UIInfo.numFilteredCerts = nicknames->numnicknames;

        if (ssm_client_auth_prepare_nicknames(conn, nicknames) != 
            SSM_SUCCESS) {
            goto loser;
        }

        /* create a cert selection dialog and get back the chosen nickname */
        if (SSM_SSLMakeClientAuthDialog(conn) != SSM_SUCCESS) {
            goto loser;
        }

        PR_ASSERT(conn->m_UIInfo.chosen >= 0);
        i = conn->m_UIInfo.chosen;
        SSM_DEBUG("Cert %s was selected.\n", conn->m_UIInfo.certNicknames[i]);

        /* first we need to extract the real nickname in case the cert
         * is an expired or not a valid one yet
         */
        extracted = CERT_ExtractNicknameString(conn->m_UIInfo.certNicknames[i],
                                               NICKNAME_EXPIRED_STRING,
                                               NICKNAME_NOT_YET_VALID_STRING);
        if (extracted == NULL) {
            goto loser;
        }

        /* find the cert under that nickname */
        node = CERT_LIST_HEAD(certList);
        while (!CERT_LIST_END(node, certList)) {
            if (PL_strcmp(node->cert->nickname, extracted) == 0) {
                cert = CERT_DupCertificate(node->cert);
                break;
            }
            node = CERT_LIST_NEXT(node);
        }
        if (cert == NULL) {
            goto loser;
        }
	
        /* go get the private key */
        privKey = PK11_FindKeyByAnyCert(cert, wincx);
        if (privKey == NULL) {
            keyError = PR_GetError();
            if (keyError == SEC_ERROR_BAD_PASSWORD) {
                /* problem with password: bail */
                goto loser;
            }
            else {
                goto noCert;
            }
        }
	}
	/* rv == SECSuccess */
	SSM_DEBUG("Yahoo!  Client auth complete.\n");

	goto done;
noCert:
loser:
    if (rv == SECSuccess) {
        rv = SECFailure;
    }
    if (cert != NULL) {
        CERT_DestroyCertificate(cert);
        cert = NULL;
    }
done:
    if (extracted != NULL) {
        PR_Free(extracted);
    }
    if (nicknames != NULL) {
        CERT_FreeNicknames(nicknames);
    }
    if (certList != NULL) {
        CERT_DestroyCertList(certList);
    }
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    *pRetCert = cert;
    *pRetKey = privKey;

	return rv;
}

/*
 * Function: SSMStatus SSM_SetUserCertChoice()

 * Purpose: sets certChoice by reading the preference
 *
 * Arguments and return values
 * - conn: SSMSSLDataConnection
 * - returns: SSM_SUCCESS if successful; SSM_FAILURE otherwise
 *
 * Note: If done properly, this function will read the identifier strings
 *		 for ASK and AUTO modes, read the selected strings from the
 *		 preference, compare the strings, and determine in which mode it is
 *		 in.
 *       We currently use ASK mode for UI apps and AUTO mode for UI-less
 *       apps without really asking for preferences.
 */
SSMStatus SSM_SetUserCertChoice(SSMSSLDataConnection* conn)
{
    SSMStatus rv;
    SSMControlConnection* parent;

    parent = SSMCONTROLCONNECTION(conn);

    if (parent->m_doesUI) {
        char* mode = NULL;

        rv = PREF_GetStringPref(parent->m_prefs, 
                                "security.default_personal_cert", &mode);
        if (rv != PR_SUCCESS) {
            return SSM_FAILURE;
        }
        if (PL_strcmp(mode, "Select Automatically") == 0) {
            certChoice = AUTO;
        }
        else if (PL_strcmp(mode, "Ask Every Time") == 0) {
            certChoice = ASK;
        }
        else {
            return SSM_FAILURE;
        }
    }
    else {
        SSM_DEBUG("UI-less app: use auto cert selection.\n");
        certChoice = AUTO;
    }

	return SSM_SUCCESS;
}


/*
 * Function: SECStatus SSM_ConvertCANamesToStrings()
 * Purpose: creates CA names strings from (CERTDistNames* caNames)
 *
 * Arguments and return values
 * - arena: arena to allocate strings on
 * - caNameStrings: filled with CA names strings on return
 * - caNames: CERTDistNames to extract strings from
 * - return: SECSuccess if successful; error code otherwise
 *
 * Note: copied in its entirety from Nova code
 */
SECStatus SSM_ConvertCANamesToStrings(PRArenaPool* arena, char** caNameStrings,
                                      CERTDistNames* caNames)
{
    SECItem* dername;
    SECStatus rv;
    int headerlen;
    uint32 contentlen;
    SECItem newitem;
    int n;
    char* namestring;

    for (n = 0; n < caNames->nnames; n++) {
        newitem.data = NULL;
        dername = &caNames->names[n];

        rv = DER_Lengths(dername, &headerlen, &contentlen);

        if (rv != SECSuccess) {
            goto loser;
        }

        if (headerlen + contentlen != dername->len) {
            /* This must be from an enterprise 2.x server, which sent
             * incorrectly formatted der without the outer wrapper of
             * type and length.  Fix it up by adding the top level
             * header.
             */
            if (dername->len <= 127) {
                newitem.data = (unsigned char *) PR_Malloc(dername->len + 2);
                if (newitem.data == NULL) {
                    goto loser;
                }
                newitem.data[0] = (unsigned char)0x30;
                newitem.data[1] = (unsigned char)dername->len;
                (void)memcpy(&newitem.data[2], dername->data, dername->len);
            }
            else if (dername->len <= 255) {
                newitem.data = (unsigned char *) PR_Malloc(dername->len + 3);
                if (newitem.data == NULL) {
                    goto loser;
                }
                newitem.data[0] = (unsigned char)0x30;
                newitem.data[1] = (unsigned char)0x81;
                newitem.data[2] = (unsigned char)dername->len;
                (void)memcpy(&newitem.data[3], dername->data, dername->len);
            }
            else {
                /* greater than 256, better be less than 64k */
                newitem.data = (unsigned char *) PR_Malloc(dername->len + 4);
                if (newitem.data == NULL) {
                    goto loser;
                }
                newitem.data[0] = (unsigned char)0x30;
                newitem.data[1] = (unsigned char)0x82;
                newitem.data[2] = (unsigned char)((dername->len >> 8) & 0xff);
                newitem.data[3] = (unsigned char)(dername->len & 0xff);
                memcpy(&newitem.data[4], dername->data, dername->len);
            }
            dername = &newitem;
        }

        namestring = CERT_DerNameToAscii(dername);
        if (namestring == NULL) {
            /* XXX - keep going until we fail to convert the name */
            caNameStrings[n] = "";
        }
        else {
            caNameStrings[n] = PORT_ArenaStrdup(arena, namestring);
            PR_Free(namestring);
            if (caNameStrings[n] == NULL) {
                goto loser;
            }
        }

        if (newitem.data != NULL) {
            PR_Free(newitem.data);
        }
    }

    return SECSuccess;
loser:
    if (newitem.data != NULL) {
        PR_Free(newitem.data);
    }
    return SECFailure;
}


/*
 * Function: SSMStatus ssm_client_auth_prepare_nicknames()
 * Purpose: (private) picks out the filtered nicknames and populates the
 *          nicknames field of the SSL connection object with them
 * Arguments and return values:
 * - conn: SSL connection object to be manipulated
 * - nicknames: nickname list
 *
 * Note: the valid nickname list is not empty if this function was called
 *       Also the memory for the nickname list is allocated here; the caller
 *       is responsible for freeing the nickname list before the client
 *       auth callback function returns
 */
SSMStatus ssm_client_auth_prepare_nicknames(SSMSSLDataConnection* conn,
                                            CERTCertNicknames* nicknames)
{
    int i;
    int number;

    PR_ASSERT(conn != NULL && conn->m_UIInfo.numFilteredCerts > 0 && 
	      nicknames != NULL);
    number = conn->m_UIInfo.numFilteredCerts;

    /* allocate memory for nickname list */
    conn->m_UIInfo.certNicknames = (char**)PR_Calloc(number, sizeof(char*));
    if (conn->m_UIInfo.certNicknames == NULL) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        goto loser;
    }

    /* fill in cert nicknames */
    for (i = 0; i < number; i++) {
        conn->m_UIInfo.certNicknames[i] = PL_strdup(nicknames->nicknames[i]);
        if (conn->m_UIInfo.certNicknames[i] == NULL) {
            goto loser;
        }
    }
    
    return SSM_SUCCESS;
loser:
    return SSM_FAILURE;
}


void SSM_SSLHandshakeCallback(PRFileDesc* socket, void* clientData)
{
	SSMSSLDataConnection* conn;
	
	PR_ASSERT(socket != NULL);
	PR_ASSERT(clientData != NULL);
	
	SSM_DEBUG("Handshake callback called.\n");
	
	conn = (SSMSSLDataConnection*)clientData;
	
	/* update the security status info */
    (void)SSMSSLDataConnection_UpdateSecurityStatus(conn);
    /* we want to proceed even if the update has failed... */

	/* check everything is hunky-dorey */    
	SSMSSLSocketStatus_Invariant(conn->m_sockStat);
}


/*
 * Function: PRBool SSM_SSLErrorNeedsDialog()
 * Purpose: sorts out errors that require dialogs
 *
 * Arguments and return values
 * - error: error code
 * - returns: PR_TRUE if dialog needed; PR_FALSE otherwise
 */
PRBool SSM_SSLErrorNeedsDialog(int error)
{
	return ((error == SEC_ERROR_UNKNOWN_ISSUER) ||
            (error == SEC_ERROR_UNTRUSTED_ISSUER) ||
            (error == SSL_ERROR_BAD_CERT_DOMAIN) ||
            (error == SSL_ERROR_POST_WARNING) ||
            (error == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) ||
            (error == SEC_ERROR_CA_CERT_INVALID) ||
            (error == SEC_ERROR_EXPIRED_CERTIFICATE));
}



SECStatus SSM_SSLMakeBadServerCertDialog(int error, 
                                         CERTCertificate* cert,
                                         SSMSSLDataConnection* conn)
{
    SECStatus rv;

    if ((error == SEC_ERROR_UNKNOWN_ISSUER) ||
        (error == SEC_ERROR_CA_CERT_INVALID) ||
        (error == SEC_ERROR_UNTRUSTED_ISSUER)) {
        rv = SSM_SSLMakeUnknownIssuerDialog(cert, conn);
    } 
    else if (error == SSL_ERROR_BAD_CERT_DOMAIN) {
        rv = SSM_SSLMakeCertBadDomainDialog(cert, conn);
    }
    else if (error == SSL_ERROR_POST_WARNING) {
        /*rv = SEC_MakeCertPostWarnDialog(proto_win, cert);*/
        rv = SECSuccess;
    }
    else if (error == SEC_ERROR_EXPIRED_CERTIFICATE) {
        rv = SSM_SSLMakeCertExpiredDialog(cert, conn);
    } 
    else if (error == SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) {
        /*rv = SEC_MakeCAExpiredDialog(proto_win, cert);*/
        rv = SECSuccess;
    }
    else {
        rv = SECFailure;
    }
    return rv;
}


SECStatus SSM_DoubleCheckServerCert(CERTCertificate* cert,
                                    SSMSSLDataConnection* conn)
{
    PRBool checkSig = PR_FALSE;
    /* ### sjlee: it is bit discomforting to set checkSig to false, but
     *     the user already decided to be generous about the cert, so it
     *     could be OK???
     */
    SSM_DEBUG("One server cert problem was handled.  See if there's more.\n");

    return SSM_SSLVerifyServerCert(SSMCONTROLCONNECTION(conn)->m_certdb, cert, 
                                   checkSig, conn);
}


/* This function does the same thing as SSL_AuthCertificate(),
** but takes different arguments.  
*/
SECStatus SSM_SSLVerifyServerCert(CERTCertDBHandle* handle,
                                  CERTCertificate* cert, PRBool checkSig, 
                                  SSMSSLDataConnection* conn)
{
    SECStatus rv;
    char* hostname = NULL;

    rv = CERT_VerifyCertNow(handle, cert, checkSig, certUsageSSLServer,
                            (void*)conn);
    if (rv != SECSuccess) {
	return rv;
    }

    /* cert is OK. we will check the name field in the cert against the
     * desired hostname.
     */
    hostname = SSL_RevealURL(conn->socketSSL);
    if (hostname && hostname[0]) {
        rv = CERT_VerifyCertName(cert, hostname);
    }
    else {
        rv = SECFailure;
    }

    if (rv != SECSuccess) {
	PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
    }
    if (hostname)
    	PR_Free(hostname);

    return rv;
}

/* The following routines are callbacks and assorted functions that are used
 * with SSL/LDAP.
 */
/* SSL client-auth callback used for SSL/LDAP */
SECStatus SSM_LDAPSSLGetClientAuthData(void* arg, PRFileDesc* socket,
                                       CERTDistNames* caNames,
                                       CERTCertificate** pRetCert,
                                       SECKEYPrivateKey** pRetKey)
{
    SECStatus rv = SECFailure;
    void* wincx = NULL;
    PRArenaPool* arena = NULL;
    char** caNameStrings = NULL;
    CERTCertList* certList = NULL;
    CERTCertListNode* node;
    CERTCertificate* cert = NULL;
    SECKEYPrivateKey* privKey = NULL;
    CERTCertDBHandle* certdb;

    SSM_DEBUG("Doing client auth for LDAP/SSL.\n");

    /* check arguments */
    if ((arg == NULL) || (socket == NULL) || (caNames == NULL) || 
        (pRetCert == NULL) || (pRetKey == NULL)) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return SECFailure;
    }

    /* get PKCS11 pin argument */
    wincx = SSL_RevealPinArg(socket);
    if (wincx == NULL) {
        return SECFailure;
    }

    /* create caNameStrings */
    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        goto loser;
    }

    caNameStrings = (char**)PORT_ArenaAlloc(arena, 
                                            sizeof(char*)*(caNames->nnames));
    if (caNameStrings == NULL) {
        goto loser;
    }

    /* XXX to do: publish the following function in sslconn.h */
    rv = SSM_ConvertCANamesToStrings(arena, caNameStrings, caNames);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* we will just do automatic cert selection for SSL/LDAP */
    /* get the cert DB */
    certdb = (CERTCertDBHandle*)arg;

    /* find all user certs that are valid and for SSL */
    certList = CERT_FindUserCertsByUsage(certdb, certUsageSSLClient, PR_TRUE,
                                         PR_TRUE, wincx);
    if (certList == NULL) {
        goto noCert;
    }

    /* filter the list to those issued by CAs supported by the server */
    rv = CERT_FilterCertListByCANames(certList, caNames->nnames, caNameStrings,
                                      certUsageSSLClient);
    if (rv != SECSuccess) {
        goto noCert;
    }

    /* make sure the list is not empty */
    node = CERT_LIST_HEAD(certList);
    if (CERT_LIST_END(node, certList)) {
        goto noCert;
    }

    /* loop through the list until we find a cert with a key */
    while (!CERT_LIST_END(node, certList)) {
        privKey = PK11_FindKeyByAnyCert(node->cert, wincx);
        if (privKey != NULL) {
            /* this is a good cert to present */
            cert = CERT_DupCertificate(node->cert);
            break;
        }
        node = CERT_LIST_NEXT(node);
    }
    if (cert == NULL) {
        goto noCert;
    }

    goto done;
noCert:
    PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
    rv = SECFailure;
loser:
    if (rv == SECSuccess) {
        rv = SECFailure;
    }
    if (cert != NULL) {
        CERT_DestroyCertificate(cert);
        cert = NULL;
    }
done:
    if (certList != NULL) {
        CERT_DestroyCertList(certList);
    }
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    *pRetCert = cert;
    *pRetKey = privKey;

    return rv;
}

/* This function must get called when the socket was created but was not
 * SSL-ified or connected
 */
SECStatus SSM_LDAPSetupSSL(PRFileDesc* socket, PRFileDesc** sslSocket,
                           CERTCertDBHandle* certdb, const char* hostname, 
                           SSMResource* caller)
{
    SECStatus rv = SECFailure;

    /* check arguments */
    if ((socket == NULL) || (sslSocket == NULL) || (certdb == NULL) ||
        (hostname == NULL) || (caller == NULL)) {
        goto loser;
    }
    *sslSocket = NULL;

    /* import the socket into SSL layer */
    *sslSocket = SSL_ImportFD(NULL, socket);
    if (*sslSocket == NULL) {
        goto loser;
    }

    /* set some SSL settings for the socket */
    rv = SSL_Enable(*sslSocket, SSL_SECURITY, PR_TRUE);
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = SSL_Enable(*sslSocket, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* set callbacks */
    rv = SSL_GetClientAuthDataHook(*sslSocket, 
                                   (SSLGetClientAuthData)SSM_LDAPSSLGetClientAuthData,
                                   (void*)certdb) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = SSL_AuthCertificateHook(*sslSocket, 
                                 (SSLAuthCertificate)SSM_SSLAuthCertificate,
                                 (void*)certdb) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = SSL_SetPKCS11PinArg(*sslSocket, (void*)caller) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    rv = SSL_SetURL(*sslSocket, hostname) == 0 ? SECSuccess : SECFailure;
    if (rv != SECSuccess) {
        goto loser;
    }

    return rv;
loser:
    if (*sslSocket == NULL) {
        if (socket != NULL) {
            PR_Close(socket);
        }
    }
    else {
        PR_Close(*sslSocket);
        *sslSocket = NULL;
    }
    return rv;
}


