/* This file implements the SERVER Session ID cache. 
 * NOTE:  The contents of this file are NOT used by the client.
 *
 * 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.
 *
 * $Id: sslsnce.c,v 1.4 2000/09/12 20:15:43 jgmyers%netscape.com Exp $
 */

/* Note: ssl_FreeSID() in sslnonce.c gets used for both client and server 
 * cache sids!
 *
 * About record locking among different server processes:
 *
 * All processes that are part of the same conceptual server (serving on 
 * the same address and port) MUST share a common SSL session cache. 
 * This code makes the content of the shared cache accessible to all
 * processes on the same "server".  This code works on Unix and Win32 only,
 * and is platform specific. 
 *
 * Unix: Multiple processes share a single (inherited) FD for a disk 
 * file all share one single file position.  If one lseeks, the position for 
 * all processes is changed.  Since the set of platforms we support do not 
 * all share portable lseek-and-read or lseek-and-write functions, a global 
 * lock must be used to make the lseek call and the subsequent read or write 
 * call be one atomic operation.  It is no longer necessary for cache element 
 * sizes to be a power of 2, or a multiple of a sector size.
 *
 * For Win32, where (a) disk I/O is not atomic, and (b) we use memory-mapped
 * files and move data to & from memory instead of calling read or write,
 * we must do explicit locking of the records for all reads and writes.
 * We have just one lock, for the entire file, using an NT semaphore.
 * We avoid blocking on "local threads" since it's bad to block on a local 
 * thread - If NSPR offered portable semaphores, it would handle this itself.
 *
 * Since this file has to do lots of platform specific I/O, the system
 * dependent error codes need to be mapped back into NSPR error codes.
 * Since NSPR's error mapping functions are private, the code is necessarily
 * duplicated in libSSL.
 *
 * Note, now that NSPR provides portable anonymous shared memory, for all
 * platforms except Mac, the implementation below should be replaced with 
 * one that uses anonymous shared memory ASAP.  This will eliminate most 
 * platform dependent code in this file, and improve performance big time.
 *
 * Now that NSPR offers portable cross-process locking (semaphores) on Unix
 * and Win32, semaphores should be used here for all platforms.
 */
#include "seccomon.h"

#if defined(XP_UNIX) || defined(XP_WIN32)
#ifndef NADA_VERISON

#include "cert.h"
#include "ssl.h"
#include "sslimpl.h"
#include "sslproto.h"
#include "pk11func.h"
#include "base64.h"

#include <stdio.h>

#ifdef XP_UNIX

#include <syslog.h>
#include <fcntl.h>
#include <unistd.h>
#include "unix_err.h"

#else /* XP_WIN32 */
#ifdef MC_HTTPD
#include <ereport.h>
#endif /* MC_HTTPD */
#include <wtypes.h>
#include "win32err.h"
#endif /* XP_WIN32 */
#include <sys/types.h>

#define SET_ERROR_CODE /* reminder */

#include "nspr.h"
#include "nsslocks.h"

static PRLock *cacheLock;

/*
** The server session-id cache uses a simple flat cache. The cache is
** sized during initialization. We hash the ip-address + session-id value
** into an index into the cache and do the lookup. No buckets, nothing
** fancy.
*/

static PRBool isMultiProcess  = PR_FALSE;

static PRUint32 numSIDCacheEntries  = 10000; 
static PRUint32 sidCacheFileSize;
static PRUint32 sidCacheWrapOffset;

static PRUint32 numCertCacheEntries = 250;
static PRUint32 certCacheFileSize;

#define MIN_CERT_CACHE_ENTRIES 125 /* the effective size in old releases. */


/*
** Format of a cache entry.
*/ 
typedef struct SIDCacheEntryStr SIDCacheEntry;
struct SIDCacheEntryStr {
    PRIPv6Addr addr;
    PRUint32 time;

    union {
	struct {
	    /* This is gross.  We have to have version and valid in both arms
	     * of the union for alignment reasons.  This probably won't work
	     * on a 64-bit machine. XXXX
	     */
/*  2 */    uint16        version;
/*  1 */    unsigned char valid;
/*  1 */    unsigned char cipherType;

/* 16 */    unsigned char sessionID[SSL_SESSIONID_BYTES];
/* 64 */    unsigned char masterKey[SSL_MAX_MASTER_KEY_BYTES];
/* 32 */    unsigned char cipherArg[SSL_MAX_CYPHER_ARG_BYTES];

/*  1 */    unsigned char masterKeyLen;
/*  1 */    unsigned char keyBits;

/*  1 */    unsigned char secretKeyBits;
/*  1 */    unsigned char cipherArgLen;
/*120 */} ssl2;

	struct {
/*  2 */    uint16           version;
/*  1 */    unsigned char    valid;
/*  1 */    uint8            sessionIDLength;

/* 32 */    unsigned char    sessionID[SSL3_SESSIONID_BYTES];

/*  2 */    ssl3CipherSuite  cipherSuite;
/*  2 */    uint16           compression; 	/* SSL3CompressionMethod */

/*122 */    ssl3SidKeys      keys;	/* keys and ivs, wrapped as needed. */
/*  4 */    PRUint32         masterWrapMech; 
/*  4 */    SSL3KEAType      exchKeyType;

/*  2 */    int16            certIndex;
/*  1 */    uint8            hasFortezza;
/*  1 */    uint8            resumable;
	} ssl3;
	/* We can't make this struct fit in 128 bytes 
	 * so, force the struct size up to the next power of two.
	 */
	struct {
	    unsigned char filler[256 - sizeof(PRIPv6Addr) - sizeof(PRUint32)];
	} force256;
    } u;
};


typedef struct CertCacheEntryStr CertCacheEntry;

/* The length of this struct is supposed to be a power of 2, e.g. 4KB */
struct CertCacheEntryStr {
    uint16        certLength;				/*    2 */
    uint16        sessionIDLength;			/*    2 */
    unsigned char sessionID[SSL3_SESSIONID_BYTES];	/*   32 */
    unsigned char cert[SSL_MAX_CACHED_CERT_LEN];	/* 4060 */
};						/* total   4096 */


static void IOError(int rv, char *type);
static PRUint32 Offset(const PRIPv6Addr *addr, unsigned char *s, unsigned nl);
static void Invalidate(SIDCacheEntry *sce);
/************************************************************************/

static const char envVarName[] = { SSL_ENV_VAR_NAME };

#ifdef _WIN32

struct winInheritanceStr {
    PRUint32 numSIDCacheEntries;
    PRUint32 sidCacheFileSize;
    PRUint32 sidCacheWrapOffset;
    PRUint32 numCertCacheEntries;
    PRUint32 certCacheFileSize;

    DWORD         parentProcessID;
    HANDLE        parentProcessHandle;
    HANDLE        SIDCacheFDMAP;
    HANDLE        certCacheFDMAP;
    HANDLE        svrCacheSem;
};
typedef struct winInheritanceStr winInheritance;

static HANDLE svrCacheSem    = INVALID_HANDLE_VALUE;

static char * SIDCacheData    = NULL;
static HANDLE SIDCacheFD      = INVALID_HANDLE_VALUE;
static HANDLE SIDCacheFDMAP   = INVALID_HANDLE_VALUE;

static char * certCacheData   = NULL;
static HANDLE certCacheFD     = INVALID_HANDLE_VALUE;
static HANDLE certCacheFDMAP  = INVALID_HANDLE_VALUE;

static PRUint32 myPid;

/* The presence of the TRUE element in this struct makes the semaphore 
 * inheritable. The NULL means use process's default security descriptor. 
 */
static SECURITY_ATTRIBUTES semaphoreAttributes = 
				{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

static SECURITY_ATTRIBUTES sidCacheFDMapAttributes = 
				{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

static SECURITY_ATTRIBUTES certCacheFDMapAttributes = 
				{ sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };

#define DEFAULT_CACHE_DIRECTORY "\\temp"

static SECStatus
createServerCacheSemaphore(void)
{
    PR_ASSERT(svrCacheSem == INVALID_HANDLE_VALUE);

    /* inheritable, starts signalled, 1 signal max, no file name. */
    svrCacheSem = CreateSemaphore(&semaphoreAttributes, 1, 1, NULL);
    if (svrCacheSem == NULL) {
	svrCacheSem = INVALID_HANDLE_VALUE;
	/* We could get the error code, but what could be do with it ? */
	nss_MD_win32_map_default_error(GetLastError());
    	return SECFailure;
    }
    return SECSuccess;
}

static SECStatus
_getServerCacheSemaphore(void)
{
    DWORD     event;
    DWORD     lastError;
    SECStatus rv;

    PR_ASSERT(svrCacheSem != INVALID_HANDLE_VALUE);
    if (svrCacheSem == INVALID_HANDLE_VALUE &&
    	SECSuccess   != createServerCacheSemaphore()) {
	return SECFailure;	/* what else ? */
    }
    event = WaitForSingleObject(svrCacheSem, INFINITE);
    switch (event) {
    case WAIT_OBJECT_0:
    case WAIT_ABANDONED:
	rv = SECSuccess;
    	break;

    case WAIT_TIMEOUT:
    case WAIT_IO_COMPLETION:
    default: 		/* should never happen. nothing we can do. */
	PR_ASSERT(("WaitForSingleObject returned invalid value.", 0));
	/* fall thru */

    case WAIT_FAILED:		/* failure returns this */
	rv = SECFailure;
	lastError = GetLastError();	/* for debugging */
	nss_MD_win32_map_default_error(lastError);
	break;
    }
    return rv;
}

static void
_doGetServerCacheSemaphore(void * arg)
{
    SECStatus * rv = (SECStatus *)arg;
    *rv = _getServerCacheSemaphore();
}

static SECStatus
getServerCacheSemaphore(void)
{
    PRThread *    selectThread;
    PRThread *    me    	= PR_GetCurrentThread();
    PRThreadScope scope 	= PR_GetThreadScope(me);
    SECStatus     rv    	= SECFailure;

    if (scope == PR_GLOBAL_THREAD) {
        rv = _getServerCacheSemaphore();
    } else {
        selectThread = PR_CreateThread(PR_USER_THREAD,
				       _doGetServerCacheSemaphore, &rv,
				       PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
				       PR_JOINABLE_THREAD, 0);
        if (selectThread != NULL) {
	    /* rv will be set by _doGetServerCacheSemaphore() */
	    PR_JoinThread(selectThread);
        }
    }
    return rv;
}

static SECStatus
releaseServerCacheSemaphore(void)
{
    BOOL success = FALSE;

    PR_ASSERT(svrCacheSem != INVALID_HANDLE_VALUE);
    if (svrCacheSem != INVALID_HANDLE_VALUE) {
	/* Add 1, don't want previous value. */
	success = ReleaseSemaphore(svrCacheSem, 1, NULL);
    }
    if (!success) {
	nss_MD_win32_map_default_error(GetLastError());
	return SECFailure;
    }
    return SECSuccess;
}

static void
destroyServerCacheSemaphore(void)
{
    PR_ASSERT(svrCacheSem != INVALID_HANDLE_VALUE);
    if (svrCacheSem != INVALID_HANDLE_VALUE) {
	CloseHandle(svrCacheSem);
	/* ignore error */
	svrCacheSem = INVALID_HANDLE_VALUE;
    }
}

#define GET_SERVER_CACHE_READ_LOCK(fd, offset, size) \
    if (isMultiProcess) getServerCacheSemaphore();

#define GET_SERVER_CACHE_WRITE_LOCK(fd, offset, size) \
    if (isMultiProcess) getServerCacheSemaphore();

#define RELEASE_SERVER_CACHE_LOCK(fd, offset, size) \
    if (isMultiProcess) releaseServerCacheSemaphore();

#endif /* _win32 */

/************************************************************************/

#ifdef XP_UNIX
static int    SIDCacheFD      = -1;
static int    certCacheFD     = -1;

static pid_t  myPid;

struct unixInheritanceStr {
    PRUint32 numSIDCacheEntries;
    PRUint32 sidCacheFileSize;
    PRUint32 sidCacheWrapOffset;
    PRUint32 numCertCacheEntries;
    PRUint32 certCacheFileSize;

    PRInt32  SIDCacheFD;
    PRInt32  certCacheFD;
};

typedef struct unixInheritanceStr unixInheritance;


#define DEFAULT_CACHE_DIRECTORY "/tmp"

#ifdef TRACE
static void 
fcntlFailed(struct flock *lock) 
{
    fprintf(stderr,
    "fcntl failed, errno = %d, PR_GetError = %d, lock.l_type = %d\n",
	errno, PR_GetError(), lock->l_type);
    fflush(stderr);
}
#define FCNTL_FAILED(lock) fcntlFailed(lock)
#else
#define FCNTL_FAILED(lock) 
#endif

/* NOTES:  Because there are no atomic seek-and-read and seek-and-write
** functions that are supported on all our UNIX platforms, we need
** to prevent all simultaeous seek-and-read operations.  For that reason,
** we use mutually exclusive (write) locks for read and write operations,
** and use them all at the same offset (zero).
*/
static SECStatus
_getServerCacheLock(int fd, short type, PRUint32 offset, PRUint32 size)
{
    int          result;
    struct flock lock;

    memset(&lock, 0, sizeof lock);
    lock.l_type   = /* type */ F_WRLCK;
    lock.l_whence = SEEK_SET;	/* absolute file offsets. */
    lock.l_start  = 0;
    lock.l_len    = 128;

#ifdef TRACE
    if (ssl_trace) {
	fprintf(stderr, "%d: %s lock, offset %8x, size %4d\n", myPid,
	    (type == F_RDLCK) ? "read " : "write", offset, size);
	fflush(stderr);
    }
#endif
    result = fcntl(fd, F_SETLKW, &lock);
    if (result == -1) {
        nss_MD_unix_map_default_error(errno);
	FCNTL_FAILED(&lock);
    	return SECFailure;
    }
#ifdef TRACE
    if (ssl_trace) {
	fprintf(stderr, "%d:   got lock, offset %8x, size %4d\n", 
	    myPid, offset, size);
	fflush(stderr);
    }
#endif
    return SECSuccess;
}

typedef struct sslLockArgsStr {
    PRUint32    offset;
    PRUint32    size;
    PRErrorCode err;
    SECStatus   rv;
    int         fd;
    short       type;
} sslLockArgs;

static void
_doGetServerCacheLock(void * arg)
{
    sslLockArgs * args = (sslLockArgs *)arg;
    args->rv = _getServerCacheLock(args->fd, args->type, args->offset, 
				   args->size );
    if (args->rv != SECSuccess) {
	args->err = PR_GetError();
    }
}

static SECStatus
getServerCacheLock(int fd, short type, PRUint32 offset, PRUint32 size)
{
    PRThread *    selectThread;
    PRThread *    me    	= PR_GetCurrentThread();
    PRThreadScope scope 	= PR_GetThreadScope(me);
    SECStatus     rv    	= SECFailure;

    if (scope == PR_GLOBAL_THREAD) {
        rv = _getServerCacheLock(fd, type, offset, size);
    } else {
	/* Ib some platforms, one thread cannot read local/automatic 
	** variables from another thread's stack.  So, get this space
	** from the heap, not the stack.
	*/
	sslLockArgs * args = PORT_New(sslLockArgs);

	if (!args)
	    return rv;

	args->offset = offset;
	args->size   = size;
	args->rv     = SECFailure;
	args->fd     = fd;
	args->type   = type;
        selectThread = PR_CreateThread(PR_USER_THREAD,
				       _doGetServerCacheLock, args,
				       PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
				       PR_JOINABLE_THREAD, 0);
        if (selectThread != NULL) {
	    /* rv will be set by _doGetServerCacheLock() */
	    PR_JoinThread(selectThread);
	    rv = args->rv;
	    if (rv != SECSuccess) {
		PORT_SetError(args->err);
	    }
        }
	PORT_Free(args);
    }
    return rv;
}

static SECStatus
releaseServerCacheLock(int fd, PRUint32 offset, PRUint32 size)
{
    int          result;
    struct flock lock;

    memset(&lock, 0, sizeof lock);
    lock.l_type   = F_UNLCK;
    lock.l_whence = SEEK_SET;	/* absolute file offsets. */
    lock.l_start  = 0;
    lock.l_len    = 128;

#ifdef TRACE
    if (ssl_trace) {
	fprintf(stderr, "%d:     unlock, offset %8x, size %4d\n", 
	    myPid, offset, size);
	fflush(stderr);
    }
#endif
    result = fcntl(fd, F_SETLK, &lock);
    if (result == -1) {
        nss_MD_unix_map_default_error(errno);
	FCNTL_FAILED(&lock);
    	return SECFailure;
    }
    return SECSuccess;
}


/* these defines take the arguments needed to do record locking, 
 * however the present implementation does only file locking.
 */

#define GET_SERVER_CACHE_READ_LOCK( fd, offset, size) \
    if (isMultiProcess) getServerCacheLock(fd, F_RDLCK, offset, size);

#define GET_SERVER_CACHE_WRITE_LOCK(fd, offset, size) \
    if (isMultiProcess) getServerCacheLock(fd, F_WRLCK, offset, size);

#define RELEASE_SERVER_CACHE_LOCK(  fd, offset, size) \
    if (isMultiProcess) releaseServerCacheLock(fd, offset, size);

/*
** Zero a file out to nb bytes
*/
static SECStatus 
ZeroFile(int fd, int nb)
{
    off_t off;
    int amount, rv;
    char buf[16384];

    PORT_Memset(buf, 0, sizeof(buf));
    off = lseek(fd, 0, SEEK_SET);
    if (off != 0) {
	if (off == -1) 
	    nss_MD_unix_map_lseek_error(errno);
    	else
	    PORT_SetError(PR_FILE_SEEK_ERROR);
    	return SECFailure;
    }

    while (nb > 0) {
	amount = (nb > sizeof buf) ? sizeof buf : nb;
	rv = write(fd, buf, amount);
	if (rv <= 0) {
	    if (!rv)
	    	PORT_SetError(PR_IO_ERROR);
	    else
		nss_MD_unix_map_write_error(errno);
	    IOError(rv, "zero-write");
	    return SECFailure;
	}
	nb -= rv;
    }
    return SECSuccess;
}

#endif /* XP_UNIX */


/************************************************************************/

/*
** Reconstitute a cert from the cache
** This is only called from ConvertToSID().
** Caller must hold the cache lock before calling this.
*/
static CERTCertificate *
GetCertFromCache(SIDCacheEntry *sce, CERTCertDBHandle *dbHandle)
{
    CERTCertificate *cert;
    PRUint32         offset;
    int              rv;
#ifdef XP_UNIX
    off_t            off;
#endif
    SECItem          derCert;
    CertCacheEntry   cce;

    offset = (PRUint32)sce->u.ssl3.certIndex * sizeof(CertCacheEntry);
    GET_SERVER_CACHE_READ_LOCK(certCacheFD, offset, sizeof(CertCacheEntry));
#ifdef XP_UNIX
    off = lseek(certCacheFD, offset, SEEK_SET);
    rv = -1;
    if (off != offset) {
	if (off == -1) 
	    nss_MD_unix_map_lseek_error(errno);
	else 
	    PORT_SetError(PR_FILE_SEEK_ERROR);
    } else {
	rv = read(certCacheFD, &cce, sizeof(CertCacheEntry));
	if (rv != sizeof(CertCacheEntry)) {
	    if (rv == -1)
		nss_MD_unix_map_read_error(errno);
	    else
	    	PORT_SetError(PR_IO_ERROR);
	}
    }
#else /* XP_WIN32 */
    /* Use memory mapped I/O and just memcpy() the data */
    CopyMemory(&cce, &certCacheData[offset], sizeof(CertCacheEntry));
    rv = sizeof cce;
#endif /* XP_WIN32 */
    RELEASE_SERVER_CACHE_LOCK(certCacheFD, offset, sizeof(CertCacheEntry))

    if (rv != sizeof(CertCacheEntry)) {
	IOError(rv, "read");	/* error set above */
	return NULL;
    }

    /* See if the session ID matches with that in the sce cache. */
    if((cce.sessionIDLength != sce->u.ssl3.sessionIDLength) ||
       PORT_Memcmp(cce.sessionID, sce->u.ssl3.sessionID, cce.sessionIDLength)) {
        /* this is a cache miss, not an error */
	PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND);
	return NULL;
    }

    derCert.len  = cce.certLength;
    derCert.data = cce.cert;

    cert = CERT_NewTempCertificate(dbHandle, &derCert, NULL,
				   PR_FALSE, PR_TRUE);

    return cert;
}

/* Put a certificate in the cache.  We assume that the certIndex in
** sid is valid.
*/
static void
CacheCert(CERTCertificate *cert, SIDCacheEntry *sce)
{
    PRUint32       offset;
    CertCacheEntry cce;
#ifdef XP_UNIX
    off_t          off;
    int            rv;
#endif

    offset = (PRUint32)sce->u.ssl3.certIndex * sizeof(CertCacheEntry);
    if (cert->derCert.len > SSL_MAX_CACHED_CERT_LEN)
	return;
    
    cce.sessionIDLength = sce->u.ssl3.sessionIDLength;
    PORT_Memcpy(cce.sessionID, sce->u.ssl3.sessionID, cce.sessionIDLength);

    cce.certLength = cert->derCert.len;
    PORT_Memcpy(cce.cert, cert->derCert.data, cce.certLength);

    GET_SERVER_CACHE_WRITE_LOCK(certCacheFD, offset, sizeof cce);
#ifdef XP_UNIX
    off = lseek(certCacheFD, offset, SEEK_SET);
    if (off != offset) {
	if (off == -1) 
	    nss_MD_unix_map_lseek_error(errno);
	else 
	    PORT_SetError(PR_FILE_SEEK_ERROR);
    } else {
	rv = write(certCacheFD, &cce, sizeof cce);
	if (rv != sizeof(CertCacheEntry)) {
	    if (rv == -1)
		nss_MD_unix_map_write_error(errno);
	    else
	    	PORT_SetError(PR_IO_ERROR);
	    IOError(rv, "cert-write");
	    Invalidate(sce);
	}
    }
#else /* WIN32 */
    /* Use memory mapped I/O and just memcpy() the data */
    CopyMemory(&certCacheData[offset], &cce, sizeof cce);
#endif /* XP_UNIX */

    RELEASE_SERVER_CACHE_LOCK(certCacheFD, offset, sizeof cce);
    return;
}

/*
** Convert memory based SID to file based one
*/
static void 
ConvertFromSID(SIDCacheEntry *to, sslSessionID *from)
{
    to->u.ssl2.valid = 1;
    to->u.ssl2.version = from->version;
    to->addr = from->addr;
    to->time = from->time;

    if (from->version < SSL_LIBRARY_VERSION_3_0) {
	if ((from->u.ssl2.masterKey.len > SSL_MAX_MASTER_KEY_BYTES) ||
	    (from->u.ssl2.cipherArg.len > SSL_MAX_CYPHER_ARG_BYTES)) {
	    SSL_DBG(("%d: SSL: masterKeyLen=%d cipherArgLen=%d",
		     myPid, from->u.ssl2.masterKey.len,
		     from->u.ssl2.cipherArg.len));
	    to->u.ssl2.valid = 0;
	    return;
	}

	to->u.ssl2.cipherType    = from->u.ssl2.cipherType;
	to->u.ssl2.masterKeyLen  = from->u.ssl2.masterKey.len;
	to->u.ssl2.cipherArgLen  = from->u.ssl2.cipherArg.len;
	to->u.ssl2.keyBits       = from->u.ssl2.keyBits;
	to->u.ssl2.secretKeyBits = from->u.ssl2.secretKeyBits;
	PORT_Memcpy(to->u.ssl2.sessionID, from->u.ssl2.sessionID,
		  sizeof(to->u.ssl2.sessionID));
	PORT_Memcpy(to->u.ssl2.masterKey, from->u.ssl2.masterKey.data,
		  from->u.ssl2.masterKey.len);
	PORT_Memcpy(to->u.ssl2.cipherArg, from->u.ssl2.cipherArg.data,
		  from->u.ssl2.cipherArg.len);
#ifdef DEBUG
	PORT_Memset(to->u.ssl2.masterKey+from->u.ssl2.masterKey.len, 0,
		  sizeof(to->u.ssl2.masterKey) - from->u.ssl2.masterKey.len);
	PORT_Memset(to->u.ssl2.cipherArg+from->u.ssl2.cipherArg.len, 0,
		  sizeof(to->u.ssl2.cipherArg) - from->u.ssl2.cipherArg.len);
#endif
	SSL_TRC(8, ("%d: SSL: ConvertSID: masterKeyLen=%d cipherArgLen=%d "
		    "time=%d addr=0x%08x%08x%08x%08x cipherType=%d", myPid,
		    to->u.ssl2.masterKeyLen, to->u.ssl2.cipherArgLen,
		    to->time, to->addr.pr_s6_addr32[0], 
		    to->addr.pr_s6_addr32[1], to->addr.pr_s6_addr32[2],
		    to->addr.pr_s6_addr32[3], to->u.ssl2.cipherType));
    } else {
	/* This is an SSL v3 session */

	to->u.ssl3.sessionIDLength  = from->u.ssl3.sessionIDLength;
	to->u.ssl3.cipherSuite      = from->u.ssl3.cipherSuite;
	to->u.ssl3.compression      = (uint16)from->u.ssl3.compression;
	to->u.ssl3.resumable        = from->u.ssl3.resumable;
	to->u.ssl3.hasFortezza      = from->u.ssl3.hasFortezza;
	to->u.ssl3.keys             = from->u.ssl3.keys;
	to->u.ssl3.masterWrapMech   = from->u.ssl3.masterWrapMech;
	to->u.ssl3.exchKeyType      = from->u.ssl3.exchKeyType;

	PORT_Memcpy(to->u.ssl3.sessionID, 
	            from->u.ssl3.sessionID,
		    from->u.ssl3.sessionIDLength);

	SSL_TRC(8, ("%d: SSL3: ConvertSID: time=%d addr=0x%08x%08x%08x%08x cipherSuite=%d",
		    myPid, to->time, to->addr.pr_s6_addr32[0],
		    to->addr.pr_s6_addr32[1], to->addr.pr_s6_addr32[2],
		    to->addr.pr_s6_addr32[3], to->u.ssl3.cipherSuite));
    }
}

/*
** Convert file based cache-entry to memory based one
** This is only called from ServerSessionIDLookup().
** Caller must hold cache lock when calling this.
*/
static sslSessionID *
ConvertToSID(SIDCacheEntry *from, CERTCertDBHandle * dbHandle)
{
    sslSessionID *to;
    uint16 version = from->u.ssl2.version;

    to = (sslSessionID*) PORT_ZAlloc(sizeof(sslSessionID));
    if (!to) {
	return 0;
    }

    if (version < SSL_LIBRARY_VERSION_3_0) {
	/* This is an SSL v2 session */
	to->u.ssl2.masterKey.data =
	    (unsigned char*) PORT_Alloc(from->u.ssl2.masterKeyLen);
	if (!to->u.ssl2.masterKey.data) {
	    goto loser;
	}
	if (from->u.ssl2.cipherArgLen) {
	    to->u.ssl2.cipherArg.data = (unsigned char*)
		PORT_Alloc(from->u.ssl2.cipherArgLen);
	    if (!to->u.ssl2.cipherArg.data) {
		goto loser;
	    }
	    PORT_Memcpy(to->u.ssl2.cipherArg.data, from->u.ssl2.cipherArg,
		      from->u.ssl2.cipherArgLen);
	}

	to->u.ssl2.cipherType    = from->u.ssl2.cipherType;
	to->u.ssl2.masterKey.len = from->u.ssl2.masterKeyLen;
	to->u.ssl2.cipherArg.len = from->u.ssl2.cipherArgLen;
	to->u.ssl2.keyBits       = from->u.ssl2.keyBits;
	to->u.ssl2.secretKeyBits = from->u.ssl2.secretKeyBits;
	PORT_Memcpy(to->u.ssl2.sessionID, from->u.ssl2.sessionID,
		  sizeof from->u.ssl2.sessionID);
	PORT_Memcpy(to->u.ssl2.masterKey.data, from->u.ssl2.masterKey,
		  from->u.ssl2.masterKeyLen);

	SSL_TRC(8, ("%d: SSL: ConvertToSID: masterKeyLen=%d cipherArgLen=%d "
		    "time=%d addr=0x%08x%08x%08x%08x cipherType=%d",
		    myPid, to->u.ssl2.masterKey.len,
		    to->u.ssl2.cipherArg.len, to->time, 
		    to->addr.pr_s6_addr32[0], to->addr.pr_s6_addr32[1],
		    to->addr.pr_s6_addr32[2], to->addr.pr_s6_addr32[3], 
		    to->u.ssl2.cipherType));
    } else {
	/* This is an SSL v3 session */

	to->u.ssl3.sessionIDLength  = from->u.ssl3.sessionIDLength;
	to->u.ssl3.cipherSuite      = from->u.ssl3.cipherSuite;
	to->u.ssl3.compression      = (SSL3CompressionMethod)from->u.ssl3.compression;
	to->u.ssl3.resumable        = from->u.ssl3.resumable;
	to->u.ssl3.hasFortezza      = from->u.ssl3.hasFortezza;
	to->u.ssl3.keys             = from->u.ssl3.keys;
	to->u.ssl3.masterWrapMech   = from->u.ssl3.masterWrapMech;
	to->u.ssl3.exchKeyType      = from->u.ssl3.exchKeyType;

	PORT_Memcpy(to->u.ssl3.sessionID, 
	            from->u.ssl3.sessionID,
		    from->u.ssl3.sessionIDLength);

	/* the portions of the SID that are only restored on the client
	 * are set to invalid values on the server.
	 */
	to->u.ssl3.clientWriteKey   = NULL;
	to->u.ssl3.serverWriteKey   = NULL;
	to->u.ssl3.tek              = NULL;
	to->urlSvrName              = NULL;

	to->u.ssl3.masterModuleID   = (SECMODModuleID)-1; /* invalid value */
	to->u.ssl3.masterSlotID     = (CK_SLOT_ID)-1;     /* invalid value */
	to->u.ssl3.masterWrapIndex  = 0;
	to->u.ssl3.masterWrapSeries = 0;
	to->u.ssl3.masterValid      = PR_FALSE;

	to->u.ssl3.clAuthModuleID   = (SECMODModuleID)-1; /* invalid value */
	to->u.ssl3.clAuthSlotID     = (CK_SLOT_ID)-1;     /* invalid value */
	to->u.ssl3.clAuthSeries     = 0;
	to->u.ssl3.clAuthValid      = PR_FALSE;

	to->u.ssl3.clientWriteSaveLen = 0;

	if (from->u.ssl3.certIndex != -1) {
	    to->peerCert = GetCertFromCache(from, dbHandle);
	    if (to->peerCert == NULL)
		goto loser;
	}
    }

    to->version    = from->u.ssl2.version;
    to->time       = from->time;
    to->cached     = in_server_cache;
    to->addr       = from->addr;
    to->references = 1;
    
    return to;

  loser:
    Invalidate(from);
    if (to) {
	if (version < SSL_LIBRARY_VERSION_3_0) {
	    if (to->u.ssl2.masterKey.data)
		PORT_Free(to->u.ssl2.masterKey.data);
	    if (to->u.ssl2.cipherArg.data)
		PORT_Free(to->u.ssl2.cipherArg.data);
	}
	PORT_Free(to);
    }
    return NULL;
}


/* Invalidate a SID cache entry. 
 * Called from CacheCert, ConvertToSid, and ServerSessionIDUncache. 
 */
static void 
Invalidate(SIDCacheEntry *sce)
{
    PRUint32	offset;
#ifdef XP_UNIX
    off_t	off;
    int 	rv;
#endif

    if (sce == NULL) return;

    if (sce->u.ssl2.version < SSL_LIBRARY_VERSION_3_0) {
	offset = Offset(&sce->addr, sce->u.ssl2.sessionID,
			sizeof sce->u.ssl2.sessionID); 
    } else {
	offset = Offset(&sce->addr, sce->u.ssl3.sessionID,
			sce->u.ssl3.sessionIDLength); 
    }
	
    sce->u.ssl2.valid = 0;
    SSL_TRC(7, ("%d: SSL: uncaching session-id at offset %ld",
		    myPid, offset));

    GET_SERVER_CACHE_WRITE_LOCK(SIDCacheFD, offset, sizeof *sce);

#ifdef XP_UNIX
    off = lseek(SIDCacheFD, offset, SEEK_SET);
    if (off != offset) {
	if (off == -1) 
	    nss_MD_unix_map_lseek_error(errno);
	else 
	    PORT_SetError(PR_FILE_SEEK_ERROR);
    } else {
	rv = write(SIDCacheFD, sce, sizeof *sce);
	if (rv != sizeof *sce) {
	    if (rv == -1)
		nss_MD_unix_map_write_error(errno);
	    else
	    	PORT_SetError(PR_IO_ERROR);
	    IOError(rv, "invalidate-write");
    	}
    }
#else /* WIN32 */
    /* Use memory mapped I/O and just memcpy() the data */
    CopyMemory(&SIDCacheData[offset], sce, sizeof *sce);
#endif /* XP_UNIX */

    RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof *sce);
}


static void 
IOError(int rv, char *type)
{
#ifdef XP_UNIX
    syslog(LOG_ALERT,
	   "SSL: %s error with session-id cache, pid=%d, rv=%d, error='%m'",
	   type, myPid, rv);
#else /* XP_WIN32 */
#ifdef MC_HTTPD 
    ereport(LOG_FAILURE, "%s error with session-id cache rv=%d\n",type, rv);
#endif /* MC_HTTPD */
#endif /* XP_UNIX */
}

static void 
lock_cache(void)
{
    PR_Lock(cacheLock);
}

static void 
unlock_cache(void)
{
    PR_Unlock(cacheLock);
}

/*
** Perform some mumbo jumbo on the ip-address and the session-id value to
** compute a hash value.
*/
static PRUint32 
Offset(const PRIPv6Addr *addr, unsigned char *s, unsigned nl)
{
    PRUint32 rv;

    rv = addr->pr_s6_addr32[3] ^ (((PRUint32)s[0] << 24) | ((PRUint32)s[1] << 16)
		 | (s[2] << 8) | s[nl-1]);
    return (rv % numSIDCacheEntries) * sizeof(SIDCacheEntry);
}



/*
** Look something up in the cache. This will invalidate old entries
** in the process. Caller has locked the cache!
** Returns PR_TRUE if found a valid match.  PR_FALSE otherwise.
*/
static PRBool 
FindSID(const PRIPv6Addr *addr, unsigned char *sessionID,
	unsigned sessionIDLength, SIDCacheEntry *sce)
{
    PRUint32      offset;
    PRUint32      now;
    int           rv;
#ifdef XP_UNIX
    off_t         off;
#endif

    /* Read in cache entry after hashing ip address and session-id value */
    offset = Offset(addr, sessionID, sessionIDLength); 
    now = ssl_Time();
    GET_SERVER_CACHE_READ_LOCK(SIDCacheFD, offset, sizeof *sce);
#ifdef XP_UNIX
    off = lseek(SIDCacheFD, offset, SEEK_SET);
    rv = -1;
    if (off != offset) {
	if (off == -1) 
	    nss_MD_unix_map_lseek_error(errno);
	else 
	    PORT_SetError(PR_FILE_SEEK_ERROR);
    } else {
	rv = read(SIDCacheFD, sce, sizeof *sce);
	if (rv != sizeof *sce) {
	    if (rv == -1) 
		nss_MD_unix_map_read_error(errno);
	    else 
		PORT_SetError(PR_IO_ERROR);
    	}
    }
#else /* XP_WIN32 */
    /* Use memory mapped I/O and just memcpy() the data */
    CopyMemory(sce, &SIDCacheData[offset], sizeof *sce);
    rv = sizeof *sce;
#endif /* XP_WIN32 */
    RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof *sce);

    if (rv != sizeof *sce) {
	IOError(rv, "server sid cache read");
	return PR_FALSE;
    }

    if (!sce->u.ssl2.valid) {
	/* Entry is not valid */
	PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND);
	return PR_FALSE;
    }

    if (((sce->u.ssl2.version < SSL_LIBRARY_VERSION_3_0) &&
	 (now > sce->time + ssl_sid_timeout)) ||
	((sce->u.ssl2.version >= SSL_LIBRARY_VERSION_3_0) &&
	 (now > sce->time + ssl3_sid_timeout))) {
	/* SessionID has timed out. Invalidate the entry. */
	SSL_TRC(7, ("%d: timed out sid entry addr=%08x%08x%08x%08x now=%x time+=%x",
		    myPid, sce->addr.pr_s6_addr32[0],
		    sce->addr.pr_s6_addr32[1], sce->addr.pr_s6_addr32[2],
		    sce->addr.pr_s6_addr32[3], now, 
		    sce->time + ssl_sid_timeout));
	sce->u.ssl2.valid = 0;

	GET_SERVER_CACHE_WRITE_LOCK(SIDCacheFD, offset, sizeof *sce);
#ifdef XP_UNIX
	off = lseek(SIDCacheFD, offset, SEEK_SET);
	rv = -1;
	if (off != offset) {
	    if (off == -1) 
		nss_MD_unix_map_lseek_error(errno);
	    else
	    	PORT_SetError(PR_IO_ERROR);
	} else {
	    rv = write(SIDCacheFD, sce, sizeof *sce);
	    if (rv != sizeof *sce) {
		if (rv == -1) 
		    nss_MD_unix_map_write_error(errno);
		else 
		    PORT_SetError(PR_IO_ERROR);
		IOError(rv, "timeout-write");
	    }
	}
#else /* WIN32 */
	/* Use memory mapped I/O and just memcpy() the data */
	CopyMemory(&SIDCacheData[offset], sce, sizeof *sce);
	rv = sizeof *sce;
#endif /* XP_UNIX */
	RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof *sce);
	if (rv == sizeof *sce)
	    PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND);
	return PR_FALSE;
    }

    /*
    ** Finally, examine specific session-id/addr data to see if the cache
    ** entry matches our addr+session-id value
    */
    if (!memcmp(&sce->addr, addr, sizeof(PRIPv6Addr)) &&
	(PORT_Memcmp(sce->u.ssl2.sessionID, sessionID, sessionIDLength) == 0)) {
	/* Found it */
	return PR_TRUE;
    }
    PORT_SetError(SSL_ERROR_SESSION_NOT_FOUND);
    return PR_FALSE;
}

/************************************************************************/

/* This is the primary function for finding entries in the server's sid cache.
 * Although it is static, this function is called via the global function 
 * pointer ssl_sid_lookup.
 */
static sslSessionID *
ServerSessionIDLookup(	const PRIPv6Addr  *addr,
			unsigned char *sessionID,
			unsigned int   sessionIDLength,
                        CERTCertDBHandle * dbHandle)
{
    SIDCacheEntry sce;
    sslSessionID *sid;

    sid = 0;
    lock_cache();
    if (FindSID(addr, sessionID, sessionIDLength, &sce)) {
	/* Found it. Convert file format to internal format */
	sid = ConvertToSID(&sce, dbHandle);
    }
    unlock_cache();
    return sid;
}

/*
** Place an sid into the cache, if it isn't already there. Note that if
** some other server process has replaced a session-id cache entry that has
** the same cache index as this sid, then all is ok. Somebody has to lose
** when this condition occurs, so it might as well be this sid.
*/
static void 
ServerSessionIDCache(sslSessionID *sid)
{
    SIDCacheEntry sce;
    PRUint32      offset;
#ifdef XP_UNIX
    off_t         off;
    int           rv;
#endif
    uint16 version = sid->version;

    if ((version >= SSL_LIBRARY_VERSION_3_0) &&
	(sid->u.ssl3.sessionIDLength == 0)) {
	return;
    }

    if (sid->cached == never_cached || sid->cached == invalid_cache) {
	lock_cache();

	sid->time = ssl_Time();
	if (version < SSL_LIBRARY_VERSION_3_0) {
	    SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x "
			"cipher=%d", myPid, sid->cached, 
			sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
			sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3],
			sid->time, sid->u.ssl2.cipherType));
	    PRINT_BUF(8, (0, "sessionID:", sid->u.ssl2.sessionID,
			  sizeof(sid->u.ssl2.sessionID)));
	    PRINT_BUF(8, (0, "masterKey:", sid->u.ssl2.masterKey.data,
			  sid->u.ssl2.masterKey.len));
	    PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data,
			  sid->u.ssl2.cipherArg.len));

	    /* Write out new cache entry */
	    offset = Offset(&sid->addr, sid->u.ssl2.sessionID,
			    sizeof(sid->u.ssl2.sessionID)); 
	} else {
	    SSL_TRC(8, ("%d: SSL: CacheMT: cached=%d addr=0x%08x%08x%08x%08x time=%x "
			"cipherSuite=%d", myPid, sid->cached, 
			sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
			sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], 
			sid->time, sid->u.ssl3.cipherSuite));
	    PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID,
			  sid->u.ssl3.sessionIDLength));

	    offset = Offset(&sid->addr, sid->u.ssl3.sessionID,
			    sid->u.ssl3.sessionIDLength);
	    
	}

	ConvertFromSID(&sce, sid);
	if (version >= SSL_LIBRARY_VERSION_3_0) {
	    if (sid->peerCert == NULL) {
		sce.u.ssl3.certIndex = -1;
	    } else {
		sce.u.ssl3.certIndex = (int16)
		    ((offset / sizeof(SIDCacheEntry)) % numCertCacheEntries);
	    }
	}
	
	GET_SERVER_CACHE_WRITE_LOCK(SIDCacheFD, offset, sizeof sce);
#ifdef XP_UNIX
	off = lseek(SIDCacheFD, offset, SEEK_SET);
	if (off != offset) {
	    if (off == -1) 
		nss_MD_unix_map_lseek_error(errno);
	    else
	    	PORT_SetError(PR_IO_ERROR);
	} else {
	    rv = write(SIDCacheFD, &sce, sizeof sce);
	    if (rv != sizeof(sce)) {
		if (rv == -1) 
		    nss_MD_unix_map_write_error(errno);
		else 
		    PORT_SetError(PR_IO_ERROR);
		IOError(rv, "update-write");
	    }
	}
#else /* WIN32 */
	CopyMemory(&SIDCacheData[offset], &sce, sizeof sce);
#endif /* XP_UNIX */
	RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof sce);

	if ((version >= SSL_LIBRARY_VERSION_3_0) && 
	    (sid->peerCert != NULL)) {
	    CacheCert(sid->peerCert, &sce);
	}

	sid->cached = in_server_cache;
	unlock_cache();
    }
}

static void 
ServerSessionIDUncache(sslSessionID *sid)
{
    SIDCacheEntry sce;
    PRErrorCode   err;
    int rv;

    if (sid == NULL) return;
    
    /* Uncaching a SID should never change the error code. 
    ** So save it here and restore it before exiting.
    */
    err = PR_GetError();
    lock_cache();
    if (sid->version < SSL_LIBRARY_VERSION_3_0) {
	SSL_TRC(8, ("%d: SSL: UncacheMT: valid=%d addr=0x%08x%08x%08x%08x time=%x "
		    "cipher=%d", myPid, sid->cached, 
		    sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
		    sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], 
		    sid->time, sid->u.ssl2.cipherType));
	PRINT_BUF(8, (0, "sessionID:", sid->u.ssl2.sessionID,
		      sizeof(sid->u.ssl2.sessionID)));
	PRINT_BUF(8, (0, "masterKey:", sid->u.ssl2.masterKey.data,
		      sid->u.ssl2.masterKey.len));
	PRINT_BUF(8, (0, "cipherArg:", sid->u.ssl2.cipherArg.data,
		      sid->u.ssl2.cipherArg.len));
	rv = FindSID(&sid->addr, sid->u.ssl2.sessionID,
		     sizeof(sid->u.ssl2.sessionID), &sce);
    } else {
	SSL_TRC(8, ("%d: SSL3: UncacheMT: valid=%d addr=0x%08x%08x%08x%08x time=%x "
		    "cipherSuite=%d", myPid, sid->cached, 
		    sid->addr.pr_s6_addr32[0], sid->addr.pr_s6_addr32[1],
		    sid->addr.pr_s6_addr32[2], sid->addr.pr_s6_addr32[3], 
		    sid->time, sid->u.ssl3.cipherSuite));
	PRINT_BUF(8, (0, "sessionID:", sid->u.ssl3.sessionID,
		      sid->u.ssl3.sessionIDLength));
	rv = FindSID(&sid->addr, sid->u.ssl3.sessionID,
		     sid->u.ssl3.sessionIDLength, &sce);
    }
    
    if (rv) {
	Invalidate(&sce);
    }
    sid->cached = invalid_cache;
    unlock_cache();
    PORT_SetError(err);
}

static SECStatus
InitSessionIDCache(int maxCacheEntries, PRUint32 timeout,
		   PRUint32 ssl3_timeout, const char *directory)
{
    char *cfn;
#ifdef XP_UNIX
    int rv;
    if (SIDCacheFD >= 0) {
	/* Already done */
	return SECSuccess;
    }
#else /* WIN32 */
	if(SIDCacheFDMAP != INVALID_HANDLE_VALUE) {
	/* Already done */
	return SECSuccess;
	}
#endif /* XP_UNIX */


    if (maxCacheEntries) {
	numSIDCacheEntries = maxCacheEntries;
    }
    sidCacheWrapOffset = numSIDCacheEntries * sizeof(SIDCacheEntry);
    sidCacheFileSize = sidCacheWrapOffset +
         (kt_kea_size * SSL_NUM_WRAP_MECHS * sizeof(SSLWrappedSymWrappingKey));

    /* Create file names */
    cfn = (char*) PORT_Alloc(PORT_Strlen(directory) + 100);
    if (!cfn) {
	return SECFailure;
    }
#ifdef XP_UNIX
    sprintf(cfn, "%s/.sslsidc.%d", directory, getpid());
#else /* XP_WIN32 */
	sprintf(cfn, "%s\\ssl.sidc.%d.%d", directory,
			GetCurrentProcessId(), GetCurrentThreadId());
#endif /* XP_WIN32 */

    /* Create session-id cache file */
#ifdef XP_UNIX
    do {
	(void) unlink(cfn);
	SIDCacheFD = open(cfn, O_EXCL|O_CREAT|O_RDWR, 0600);
    } while (SIDCacheFD < 0 && errno == EEXIST);
    if (SIDCacheFD < 0) {
	nss_MD_unix_map_open_error(errno);
	IOError(SIDCacheFD, "create");
	goto loser;
    }
    rv = unlink(cfn);
    if (rv < 0) {
	nss_MD_unix_map_unlink_error(errno);
	IOError(rv, "unlink");
	goto loser;
    }
#else  /* WIN32 */
    SIDCacheFDMAP = 
    	CreateFileMapping(INVALID_HANDLE_VALUE, /* allocate in swap file */
			  &sidCacheFDMapAttributes, /* inheritable. */
			  PAGE_READWRITE,
			  0,                    /* size, high word. */
			  sidCacheFileSize,     /* size, low  word. */
			  NULL);		/* no map name in FS */
    if(! SIDCacheFDMAP) {
	nss_MD_win32_map_default_error(GetLastError());
	goto loser;
    }
    SIDCacheData = (char *)MapViewOfFile(SIDCacheFDMAP, 
                                         FILE_MAP_ALL_ACCESS, 	/* R/W    */
					 0, 0, 			/* offset */
				         sidCacheFileSize);	/* size   */
    if (! SIDCacheData) {
	nss_MD_win32_map_default_error(GetLastError());
	goto loser;
    }
#endif /* XP_UNIX */

    if (!cacheLock)
	nss_InitLock(&cacheLock);
    if (!cacheLock) {
	SET_ERROR_CODE
	goto loser;
    }
#ifdef _WIN32
    if (isMultiProcess  && (SECSuccess != createServerCacheSemaphore())) {
	SET_ERROR_CODE
	goto loser;
    }
#endif

    if (timeout) {   
	if (timeout > 100) {
	    timeout = 100;
	}
	if (timeout < 5) {
	    timeout = 5;
	}
	ssl_sid_timeout = timeout;
    }

    if (ssl3_timeout) {   
	if (ssl3_timeout > 86400L) {
	    ssl3_timeout = 86400L;
	}
	if (ssl3_timeout < 5) {
	    ssl3_timeout = 5;
	}
	ssl3_sid_timeout = ssl3_timeout;
    }

    GET_SERVER_CACHE_WRITE_LOCK(SIDCacheFD, 0, sidCacheFileSize);
#ifdef XP_UNIX
    /* Initialize the files */
    if (ZeroFile(SIDCacheFD, sidCacheFileSize)) {
	/* Bummer */
	close(SIDCacheFD);
	SIDCacheFD = -1;
	goto loser;
    }
#else /* XP_WIN32 */
    ZeroMemory(SIDCacheData, sidCacheFileSize);
#endif /* XP_UNIX */
    RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, 0, sidCacheFileSize);
    PORT_Free(cfn);
    return SECSuccess;

  loser:
#ifdef _WIN32
    if (svrCacheSem)
	destroyServerCacheSemaphore();
#endif
    if (cacheLock) {
	PR_DestroyLock(cacheLock);
	cacheLock = NULL;
    }
    PORT_Free(cfn);
    return SECFailure;
}

static SECStatus 
InitCertCache(const char *directory)
{
    char *cfn;
#ifdef XP_UNIX
    int rv;
    if (certCacheFD >= 0) {
	/* Already done */
	return SECSuccess;
    }
#else /* WIN32 */
    if(certCacheFDMAP != INVALID_HANDLE_VALUE) {
	/* Already done */
	return SECSuccess;
    }
#endif /* XP_UNIX */

    numCertCacheEntries = sidCacheFileSize / sizeof(CertCacheEntry);
    if (numCertCacheEntries < MIN_CERT_CACHE_ENTRIES)
    	numCertCacheEntries = MIN_CERT_CACHE_ENTRIES;
    certCacheFileSize = numCertCacheEntries * sizeof(CertCacheEntry);

    /* Create file names */
    cfn = (char*) PORT_Alloc(PORT_Strlen(directory) + 100);
    if (!cfn) {
	return SECFailure;
    }
#ifdef XP_UNIX
    sprintf(cfn, "%s/.sslcertc.%d", directory, getpid());
#else /* XP_WIN32 */
    sprintf(cfn, "%s\\ssl.certc.%d.%d", directory,
	    GetCurrentProcessId(), GetCurrentThreadId());
#endif /* XP_WIN32 */

    /* Create certificate cache file */
#ifdef XP_UNIX
    do {
	(void) unlink(cfn);
	certCacheFD = open(cfn, O_EXCL|O_CREAT|O_RDWR, 0600);
    } while (certCacheFD < 0 && errno == EEXIST);
    if (certCacheFD < 0) {
	nss_MD_unix_map_open_error(errno);
	IOError(certCacheFD, "create");
	goto loser;
    }
    rv = unlink(cfn);
    if (rv < 0) {
	nss_MD_unix_map_unlink_error(errno);
	IOError(rv, "unlink");
	goto loser;
    }
#else  /* WIN32 */
    certCacheFDMAP = 
    	CreateFileMapping(INVALID_HANDLE_VALUE, /* allocate in swap file */
			  &certCacheFDMapAttributes, /* inheritable. */
			  PAGE_READWRITE,
			  0,                /* size, high word. */
			  certCacheFileSize, /* size, low word. */
			  NULL);           /* no map name in FS */
    if (! certCacheFDMAP) {
	nss_MD_win32_map_default_error(GetLastError());
	goto loser;
    }
    certCacheData = (char *) MapViewOfFile(certCacheFDMAP, 
                                           FILE_MAP_ALL_ACCESS, /* R/W    */
					   0, 0, 		/* offset */
					   certCacheFileSize);	/* size   */
    if (! certCacheData) {
	nss_MD_win32_map_default_error(GetLastError());
	goto loser;
    }
#endif /* XP_UNIX */

/*  GET_SERVER_CACHE_WRITE_LOCK(certCacheFD, 0, certCacheFileSize); */
#ifdef XP_UNIX
    /* Initialize the files */
    if (ZeroFile(certCacheFD, certCacheFileSize)) {
	/* Bummer */
	close(certCacheFD);
	certCacheFD = -1;
	goto loser;
    }
#else /* XP_WIN32 */
    ZeroMemory(certCacheData, certCacheFileSize);
#endif /* XP_UNIX */
/*  RELEASE_SERVER_CACHE_LOCK(certCacheFD, 0, certCacheFileSize);   */
    PORT_Free(cfn);
    return SECSuccess;

  loser:
    PORT_Free(cfn);
    return SECFailure;
}

int
SSL_ConfigServerSessionIDCache(	int      maxCacheEntries, 
				PRUint32 timeout,
			       	PRUint32 ssl3_timeout, 
			  const char *   directory)
{
    SECStatus rv;

    PORT_Assert(sizeof(SIDCacheEntry) == 256);
    PORT_Assert(sizeof(CertCacheEntry) == 4096);

    myPid = SSL_GETPID();
    if (!directory) {
	directory = DEFAULT_CACHE_DIRECTORY;
    }
    rv = InitSessionIDCache(maxCacheEntries, timeout, ssl3_timeout, directory);
    if (rv) {
	SET_ERROR_CODE
    	return SECFailure;
    }
    rv = InitCertCache(directory);
    if (rv) {
	SET_ERROR_CODE
    	return SECFailure;
    }

    ssl_sid_lookup  = ServerSessionIDLookup;
    ssl_sid_cache   = ServerSessionIDCache;
    ssl_sid_uncache = ServerSessionIDUncache;
    return SECSuccess;
}

/* Use this function, instead of SSL_ConfigServerSessionIDCache,
 * if the cache will be shared by multiple processes.
 */
int
SSL_ConfigMPServerSIDCache(	int      maxCacheEntries, 
				PRUint32 timeout,
			       	PRUint32 ssl3_timeout, 
		          const char *   directory)
{
    char *	envValue;
    int 	result;
    SECStatus	putEnvFailed;

    isMultiProcess = PR_TRUE;
    result = SSL_ConfigServerSessionIDCache(maxCacheEntries, timeout, 
                                            ssl3_timeout, directory);
    if (result == SECSuccess) {
#ifdef _WIN32
	winInheritance winherit;

	winherit.numSIDCacheEntries	= numSIDCacheEntries;
	winherit.sidCacheFileSize	= sidCacheFileSize;
	winherit.sidCacheWrapOffset	= sidCacheWrapOffset;
	winherit.numCertCacheEntries	= numCertCacheEntries;
	winherit.certCacheFileSize	= certCacheFileSize;
	winherit.SIDCacheFDMAP		= SIDCacheFDMAP;
	winherit.certCacheFDMAP		= certCacheFDMAP;
	winherit.svrCacheSem		= svrCacheSem;
	winherit.parentProcessID	= GetCurrentProcessId();
	winherit.parentProcessHandle	= 
	    OpenProcess(PROCESS_DUP_HANDLE, TRUE, winherit.parentProcessID);
        if (winherit.parentProcessHandle == NULL) {
	    SET_ERROR_CODE
	    return SECFailure;
	}
        envValue = BTOA_DataToAscii((unsigned char *)&winherit, 
	                             sizeof winherit);
    	if (!envValue) {
	    SET_ERROR_CODE
	    return SECFailure;
	}
#else
	unixInheritance uinherit;

	uinherit.numSIDCacheEntries	= numSIDCacheEntries;
	uinherit.sidCacheFileSize	= sidCacheFileSize;
	uinherit.sidCacheWrapOffset	= sidCacheWrapOffset;
	uinherit.numCertCacheEntries	= numCertCacheEntries;
	uinherit.certCacheFileSize	= certCacheFileSize;
	uinherit.SIDCacheFD		= SIDCacheFD;
	uinherit.certCacheFD		= certCacheFD;

        envValue = BTOA_DataToAscii((unsigned char *)&uinherit, 
	                             sizeof uinherit);
    	if (!envValue) {
	    SET_ERROR_CODE
	    return SECFailure;
	}
#endif
    }
    putEnvFailed = (SECStatus)NSS_PutEnv(envVarName, envValue);
    PORT_Free(envValue);
    if (putEnvFailed) {
        SET_ERROR_CODE
        result = SECFailure;
    }
    return result;
}

SECStatus
SSL_InheritMPServerSIDCache(const char * envString)
{
    unsigned char * decoString = NULL;
    unsigned int    decoLen;
#ifdef _WIN32
    winInheritance  inherit;
#else
    unixInheritance inherit;
#endif

    myPid = SSL_GETPID();
    if (isMultiProcess)
    	return SECSuccess;	/* already done. */

    ssl_sid_lookup  = ServerSessionIDLookup;
    ssl_sid_cache   = ServerSessionIDCache;
    ssl_sid_uncache = ServerSessionIDUncache;

    if (!envString) {
    	envString  = getenv(envVarName);
	if (!envString) {
	    SET_ERROR_CODE
	    return SECFailure;
	}
    }

    decoString = ATOB_AsciiToData(envString, &decoLen);
    if (!decoString) {
    	SET_ERROR_CODE
	return SECFailure;
    }
    if (decoLen != sizeof inherit) {
    	SET_ERROR_CODE
    	goto loser;
    }

    PORT_Memcpy(&inherit, decoString, sizeof inherit);
    PORT_Free(decoString);

    numSIDCacheEntries	= inherit.numSIDCacheEntries;
    sidCacheFileSize	= inherit.sidCacheFileSize;
    sidCacheWrapOffset	= inherit.sidCacheWrapOffset;
    numCertCacheEntries	= inherit.numCertCacheEntries;
    certCacheFileSize	= inherit.certCacheFileSize;

#ifdef _WIN32
    SIDCacheFDMAP	= inherit.SIDCacheFDMAP;
    certCacheFDMAP	= inherit.certCacheFDMAP;
    svrCacheSem		= inherit.svrCacheSem;

#if 0
    /* call DuplicateHandle ?? */
    inherit.parentProcessID;
    inherit.parentProcessHandle;
#endif

    if(!SIDCacheFDMAP) {
    	SET_ERROR_CODE
    	goto loser;
    }
    SIDCacheData = (char *)MapViewOfFile(SIDCacheFDMAP, 
                                         FILE_MAP_ALL_ACCESS, 	/* R/W    */
					 0, 0, 			/* offset */
				         sidCacheFileSize);	/* size   */
    if(!SIDCacheData) {
	nss_MD_win32_map_default_error(GetLastError());
    	goto loser;
    }

    if(!certCacheFDMAP) {
    	SET_ERROR_CODE
    	goto loser;
    }
    certCacheData = (char *) MapViewOfFile(certCacheFDMAP, 
                                           FILE_MAP_ALL_ACCESS, /* R/W    */
					   0, 0, 		/* offset */
					   certCacheFileSize);	/* size   */
    if(!certCacheData) {
	nss_MD_win32_map_default_error(GetLastError());
    	goto loser;
    }

#else /* must be unix */
    SIDCacheFD		= inherit.SIDCacheFD;
    certCacheFD		= inherit.certCacheFD;
    if (SIDCacheFD < 0 || certCacheFD < 0) {
    	SET_ERROR_CODE
    	goto loser;
    }
#endif

    if (!cacheLock) {
	nss_InitLock(&cacheLock);
	if (!cacheLock) 
	    goto loser;
    }
    isMultiProcess = PR_TRUE;
    return SECSuccess;

loser:
    if (decoString) 
	PORT_Free(decoString);
#if _WIN32
    if (SIDCacheFDMAP) {
	CloseHandle(SIDCacheFDMAP);
	SIDCacheFDMAP = NULL;
    }
    if (certCacheFDMAP) {
	CloseHandle(certCacheFDMAP);
	certCacheFDMAP = NULL;
    }
#else
    if (SIDCacheFD >= 0) {
	close(SIDCacheFD);
	SIDCacheFD = -1;
    }
    if (certCacheFD >= 0) {
	close(certCacheFD);
	certCacheFD = -1;
    }
#endif
    return SECFailure;

}

/************************************************************************
 *  Code dealing with shared wrapped symmetric wrapping keys below      *
 ************************************************************************/


static PRBool
getWrappingKey(PRInt32                   symWrapMechIndex,
               SSL3KEAType               exchKeyType, 
               SSLWrappedSymWrappingKey *wswk, 
               PRBool                    grabSharedLock)
{
    PRUint32      offset = sidCacheWrapOffset + 
	       ((exchKeyType * SSL_NUM_WRAP_MECHS + symWrapMechIndex) *
		   sizeof(SSLWrappedSymWrappingKey));
    PRBool        rv     = PR_TRUE;
#ifdef XP_UNIX
    off_t         lrv;
    ssize_t       rrv;
#endif

    if (grabSharedLock) {
	GET_SERVER_CACHE_READ_LOCK(SIDCacheFD, offset, sizeof *wswk);
    }

#ifdef XP_UNIX
    lrv = lseek(SIDCacheFD, offset, SEEK_SET);
    if (lrv != offset) {
	if (lrv == -1) 
	    nss_MD_unix_map_lseek_error(errno);
	else
	    PORT_SetError(PR_IO_ERROR);
	    IOError(rv, "wrapping-read");
	rv = PR_FALSE;
    } else {
	rrv = read(SIDCacheFD, wswk, sizeof *wswk);
	if (rrv != sizeof *wswk) {
	    if (rrv == -1) 
		nss_MD_unix_map_read_error(errno);
	    else 
		PORT_SetError(PR_IO_ERROR);
	    IOError(rv, "wrapping-read");
	    rv = PR_FALSE;
	}
    }
#else /* XP_WIN32 */
    /* Use memory mapped I/O and just memcpy() the data */
    CopyMemory(wswk, &SIDCacheData[offset], sizeof *wswk);
#endif /* XP_WIN32 */
    if (grabSharedLock) {
	RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof *wswk);
    }
    if (rv) {
    	if (wswk->exchKeyType      != exchKeyType || 
	    wswk->symWrapMechIndex != symWrapMechIndex ||
	    wswk->wrappedSymKeyLen == 0) {
	    memset(wswk, 0, sizeof *wswk);
	    rv = PR_FALSE;
	}
    }
    return rv;
}

PRBool
ssl_GetWrappingKey( PRInt32                   symWrapMechIndex,
                    SSL3KEAType               exchKeyType, 
		    SSLWrappedSymWrappingKey *wswk)
{
    PRBool rv;

    lock_cache();

    PORT_Assert( (unsigned)exchKeyType < kt_kea_size);
    PORT_Assert( (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS);
    if ((unsigned)exchKeyType < kt_kea_size &&
        (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS) {
	rv = getWrappingKey(symWrapMechIndex, exchKeyType, wswk, PR_TRUE);
    } else {
    	rv = PR_FALSE;
    }
    unlock_cache();
    return rv;
}

/* The caller passes in the new value it wants
 * to set.  This code tests the wrapped sym key entry in the file on disk.  
 * If it is uninitialized, this function writes the caller's value into 
 * the disk entry, and returns false.  
 * Otherwise, it overwrites the caller's wswk with the value obtained from 
 * the disk, and returns PR_TRUE.  
 * This is all done while holding the locks/semaphores necessary to make 
 * the operation atomic.
 */
PRBool
ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk)
{
    PRBool        rv;
    SSL3KEAType   exchKeyType      = wswk->exchKeyType;   
                                /* type of keys used to wrap SymWrapKey*/
    PRInt32       symWrapMechIndex = wswk->symWrapMechIndex;
    PRUint32      offset;
    SSLWrappedSymWrappingKey myWswk;

    PORT_Assert( (unsigned)exchKeyType < kt_kea_size);
    if ((unsigned)exchKeyType >= kt_kea_size)
    	return 0;

    PORT_Assert( (unsigned)symWrapMechIndex < SSL_NUM_WRAP_MECHS);
    if ((unsigned)symWrapMechIndex >=  SSL_NUM_WRAP_MECHS)
    	return 0;

    offset = sidCacheWrapOffset + 
	       ((exchKeyType * SSL_NUM_WRAP_MECHS + symWrapMechIndex) *
		   sizeof(SSLWrappedSymWrappingKey));
    PORT_Memset(&myWswk, 0, sizeof myWswk);	/* eliminate UMRs. */
    lock_cache();
    GET_SERVER_CACHE_WRITE_LOCK(SIDCacheFD, offset, sizeof *wswk);

    rv = getWrappingKey(wswk->symWrapMechIndex, wswk->exchKeyType, &myWswk, 
                        PR_FALSE);
    if (rv) {
	/* we found it on disk, copy it out to the caller. */
	PORT_Memcpy(wswk, &myWswk, sizeof *wswk);
    } else {
	/* Wasn't on disk, and we're still holding the lock, so write it. */

#ifdef XP_UNIX
	off_t         lrv;
	ssize_t       rrv;

	lrv = lseek(SIDCacheFD, offset, SEEK_SET);
	if (lrv != offset) {
	    if (lrv == -1) 
		nss_MD_unix_map_lseek_error(errno);
	    else
		PORT_SetError(PR_IO_ERROR);
	    IOError(rv, "wrapping-read");
	    rv = PR_FALSE;
	} else {
	    rrv = write(SIDCacheFD, wswk, sizeof *wswk);
	    if (rrv != sizeof *wswk) {
		if (rrv == -1) 
		    nss_MD_unix_map_read_error(errno);
		else 
		    PORT_SetError(PR_IO_ERROR);
		IOError(rv, "wrapping-read");
		rv = PR_FALSE;
	    }
	}
#else /* XP_WIN32 */
	/* Use memory mapped I/O and just memcpy() the data */
	CopyMemory(&SIDCacheData[offset], wswk, sizeof *wswk);
#endif /* XP_WIN32 */
    }
    RELEASE_SERVER_CACHE_LOCK(SIDCacheFD, offset, sizeof *wswk);
    unlock_cache();
    return rv;
}


#endif /* NADA_VERISON */
#else

#include "seccomon.h"
#include "cert.h"
#include "ssl.h"
#include "sslimpl.h"

PRBool
ssl_GetWrappingKey( PRInt32                   symWrapMechIndex,
                    SSL3KEAType               exchKeyType, 
		    SSLWrappedSymWrappingKey *wswk)
{
    PRBool rv = PR_FALSE;
    PR_ASSERT(!"SSL servers are not supported on the Mac. (ssl_GetWrappingKey)");    
    return rv;
}

/* This is a kind of test-and-set.  The caller passes in the new value it wants
 * to set.  This code tests the wrapped sym key entry in the file on disk.  
 * If it is uninitialized, this function writes the caller's value into 
 * the disk entry, and returns false.  
 * Otherwise, it overwrites the caller's wswk with the value obtained from 
 * the disk, and returns PR_TRUE.  
 * This is all done while holding the locks/semaphores necessary to make 
 * the operation atomic.
 */
PRBool
ssl_SetWrappingKey(SSLWrappedSymWrappingKey *wswk)
{
    PRBool        rv = PR_FALSE;
    PR_ASSERT(!"SSL servers are not supported on the Mac. (ssl_SetWrappingKey)");
    return rv;
}

#endif /* XP_UNIX || XP_WIN32 */
