/*
 * The contents of this file are subject to the AOLserver 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://aolserver.com.
 *
 * 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.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of 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
 * License, 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 License or the GPL.
 *
 * Copyright (C) 2000-2003 Scott S. Goodwin
 *
 * Module originally written by Stefan Arentz. Early contributions made by
 * Freddie Mendoze and Rob Mayoff.
 *
 * Portions created by AOL are Copyright (C) 1999 America Online, Inc.
 * All Rights Reserved.
 */

/*
 * nsopenssl.c --
 *
 *    Implements SSLv2, SSLv3 and TLSv1 module using OpenSSL.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/nsopenssl/nsopenssl.c,v 1.77 2004/11/20 06:42:54 dossy Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsopenssl.h"

extern Tcl_HashTable NsOpenSSLServers;
extern void     NsOpenSSLDriversLoad(char *server);
extern int      NsMakeTmpRSAKey(int keylen);

static Ns_Mutex *locks;
static Ns_DriverProc OpenSSLProc;

static int      InitOpenSSL(void);
static void     InitServerState(char *server);
static int      SeedPRNG(void);
static void     ThreadLockCallback(int mode, int n, const char *file, int line);
static unsigned long ThreadIdCallback(void);
static struct CRYPTO_dynlock_value *ThreadDynlockCreateCallback(char *file, int line);
static void     ThreadDynlockLockCallback(int mode, struct CRYPTO_dynlock_value *dynlock, const char *file, int line);
static void     ThreadDynlockDestroyCallback(struct CRYPTO_dynlock_value *dynlock, const char *file, int line);
static void     ServerShutdown(void *arg);
static void     LoadSSLContexts(char *server);
static NsOpenSSLContext *LoadSSLContext(char *server, char *name);
static int      InitSSLDriver(char *server, NsOpenSSLDriver *ssldriver);
static void     LoadSSLDrivers(char *server);

int Ns_ModuleVersion = 1;


/*
 *----------------------------------------------------------------------
 *
 * Ns_ModuleInit --
 *
 *     nsopenssl module initialization.
 *
 * Results:
 *     NS_OK or NS_ERROR
 *
 * Side effects:
 *
 *----------------------------------------------------------------------
 */

int
Ns_ModuleInit(char *server, char *module)
{
    static int  initialized = NS_FALSE;
    int         i;

    /* 
     * Initialize one-time global stuff.
     */

    if (initialized == NS_FALSE) {
        if (!STREQ(module, MODULE)) {
            Ns_Log(Fatal, "Module '%s' should be named '%s'", module, MODULE);
        }
        if (InitOpenSSL() == NS_ERROR) {
            Ns_Log(Error, "%s: OpenSSL failed to initialize", MODULE);
            return NS_ERROR;
        }
        Tcl_InitHashTable(&NsOpenSSLServers, TCL_STRING_KEYS);

        /*
         * Pre-generate temporary RSA keys for 512 and 1024 bit keys.
         */

        NsMakeTmpRSAKey(512);
        NsMakeTmpRSAKey(1024);

        initialized = NS_TRUE;
    }

    /* 
     * Initialize this virtual server's state information 
     */

    InitServerState(server);

    /* 
     * Load this virtual server's SSL contexts from the configuration file.
     */

    LoadSSLContexts(server);

    /*
     * Load and start the driver(s) for this virtual server.  A driver manages
     * one SSL port; for a virtual server to use more than one port, you must
     * define a driver for each port.  A driver must be associated with a named
     * SSL context.
     */

    LoadSSLDrivers(server);

    /* 
     * Create the nsopenssl Tcl commands for this virtual server's interps.
     */

    NsOpenSSLTclInit(server);

    /*
     * Register a cleanup function to run at server shutdown time.
     */

    Ns_RegisterAtShutdown(ServerShutdown, (void *) server);

    return NS_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NsOpenSSLDebug --
 *
 *    Write message to log file. When not in debug mode, no logging is done,
 *    function just returns.
 *
 *----------------------------------------------------------------------
 */

#if 0
extern void
NsOpenSSLDebug(char *fmt, ...)
{
    va_list ap;
    Ns_LogSeverity severity = "Debug";
    char buf[1000];

    //va_start(ap, fmt);
    sprintf(&buf, fmt, ap);
    Ns_Log(severity, &buf);
    //va_end(ap);

}
#endif



/*
 *----------------------------------------------------------------------
 *
 * InitServerState --
 *
 *     Initialize a virtual server's state storage. This holds pointers to SSL
 *     contexts stored by name, as well as default client and server SSL
 *     contexts to use in cases where the programmer didn't explicitly name one
 *     to use.
 *
 * Results:
 *
 * Side effects:
 *
 *----------------------------------------------------------------------
 */

static void
InitServerState(char *server)
{
    Server        *thisServer = NULL;
    Tcl_HashEntry *hPtr       = NULL;
    int            new        = 0;
    Ns_DString     ds;
    char          *lockName   = NULL;

    Ns_DStringInit(&ds);
    thisServer = ns_malloc(sizeof(Server));
    if (thisServer == NULL) {
	Ns_Log(Fatal, "%s (%s): memory allocation failed");
    }
    thisServer->server = server;
    thisServer->defaultservercontext = NULL;
    thisServer->defaultclientcontext = NULL;
    thisServer->nextSessionCacheId = 1;
    Ns_MutexInit(&thisServer->lock);
    Ns_DStringPrintf(&ds, "server:%s", server);
    lockName = Ns_DStringExport(&ds);
    Ns_MutexSetName2(&thisServer->lock, MODULE_SHORT, lockName);
    Ns_DStringTrunc(&ds, 0);
    ns_free(lockName);
    lockName = NULL;
    hPtr = Tcl_CreateHashEntry(&NsOpenSSLServers, server, &new);
    Tcl_SetHashValue(hPtr, thisServer);
    Tcl_InitHashTable(&thisServer->sslcontexts, TCL_STRING_KEYS);
    Tcl_InitHashTable(&thisServer->ssldrivers, TCL_STRING_KEYS);

    return;
}


/*
 *----------------------------------------------------------------------
 *
 * LoadSSLContexts --
 *
 *       Load the SSL context for a virtual server.
 *
 * Results:
 *       NS_OK or NS_ERROR
 *
 * Side effects:
 *       Registers driver with AOLserver core.
 *
 *----------------------------------------------------------------------
 */

static void
LoadSSLContexts(char *server)
{
    Server           *thisServer  = NsOpenSSLServerGet(server);
    NsOpenSSLContext *sslcontext  = NULL;
    Ns_Set           *sslcontexts = NULL;
    Ns_Set           *defaults    = NULL;
    char             *path        = NULL;
    char             *name        = NULL;
    char             *value       = NULL;
    int               i           = 0;

    path = Ns_ConfigGetPath(server, MODULE, "sslcontexts", NULL);
    sslcontexts = Ns_ConfigGetSection(path);

    /* 
     * If no SSL contexts are defined for this virtual server, we won't start
     * any drivers for it, and we won't be able to use SSL at all. I'll soon be
     * adding the capability to build SSL contexts with the Tcl API dynamically
     * so it will make sense to do this at some point in the future if you want
     * to manage SSL conns but not through the AOLserver comm API.
     */

    if (sslcontexts == NULL) {
        Ns_Log(Notice, "%s (%s): no SSL contexts defined for this server", 
                server, MODULE);
        return;
    }
    for (i = 0; i < Ns_SetSize(sslcontexts); ++i) {
        name = Ns_SetKey(sslcontexts, i);
        Ns_Log(Notice, "%s (%s): loading SSL context '%s'", MODULE, server, name);
        sslcontext = LoadSSLContext(server, name);
        NsOpenSSLContextAdd(server, sslcontext);
        if (NsOpenSSLContextInit(server, sslcontext) == NS_ERROR) {
            Ns_Log(Error, "%s (%s): SSL context '%s' left uninitialized",
                    MODULE, server, name);
        }
    }

    /*
     * Set default server SSL client and server contexts. These are used in
     * cases where the C or Tcl programmer does not specify what named SSL
     * context to use.
     */

    path = Ns_ConfigGetPath(server, MODULE, "defaults", NULL);
    defaults = Ns_ConfigGetSection(path);
    if (defaults == NULL) {
        Ns_Log(Notice, "%s (%s): no default SSL contexts defined for this server", 
                MODULE, server);
        return;
    }
    for (i = 0; i < Ns_SetSize(defaults); ++i) {
        name = Ns_SetKey(defaults, i);
        value = Ns_ConfigGetValue(path, name);
        sslcontext = Ns_OpenSSLServerSSLContextGet(server, value);
        if (sslcontext != NULL) {
            Ns_Log(Notice, "%s (%s): default SSL context for %s is %s", MODULE, server, name, value);
            if (STRIEQ(name, "server")) {
                thisServer->defaultservercontext = value;
                Ns_Log(Notice, "default server SSL context: %s", thisServer->defaultservercontext);
            } else if (STRIEQ(name, "client")) {
                thisServer->defaultclientcontext = value;
                Ns_Log(Notice, "default client SSL context: %s", thisServer->defaultclientcontext);
            } else {
                Ns_Log(Error, "%s (%s): bad parameter '%s' for default contexts",
                        MODULE, server, name);
            }
        } else {
            Ns_Log(Error, "%s (%s): SSL context '%s' doesn't exist; can't use it as a default",
                    MODULE, server, value);
        }
    }
}


/*
 *----------------------------------------------------------------------
 *
 * LoadSSLContext --
 *
 *     Load values for a given SSL context from the configuration file.
 *
 * Results:
 *     Pointer to SSL Context or NULL
 *
 * Side effects:
 *     Memory may be allocated
 *
 *----------------------------------------------------------------------
 */

static NsOpenSSLContext *
LoadSSLContext(char *server, char *name)
{
    NsOpenSSLContext *sslcontext           = NULL;
    char             *role                 = NULL;
    char             *path                 = NULL;
    char             *moduleDir            = NULL;
    char             *certFile             = NULL;
    char             *keyFile              = NULL;
    char             *caFile               = NULL;
    char             *caDir                = NULL;
    char             *protocols            = NULL;
    char             *cipherSuite          = NULL;
    int               sessionCache         = DEFAULT_SESSION_CACHE;
    int               sessionCacheSize     = DEFAULT_SESSION_CACHE_SIZE;
    int               sessionCacheTimeout  = DEFAULT_SESSION_CACHE_TIMEOUT;
    int               peerVerify           = DEFAULT_PEER_VERIFY;
    int               peerVerifyDepth      = DEFAULT_PEER_VERIFY_DEPTH;
    int               trace                = DEFAULT_TRACE;

    path = Ns_ConfigGetPath(server, MODULE, "sslcontext", name, NULL);
    if (path == NULL) {
        Ns_Log(Error, "%s (%s): failed to find SSL context '%s' in configuration file",
                MODULE, server, name);
        return NULL;
    }
    sslcontext = NsOpenSSLContextCreate(server, name);

    /*
     * Must be "client" or "server"
     */

    role = Ns_ConfigGetValue(path, "role");
    if (role != NULL) {
        NsOpenSSLContextRoleSet(server, sslcontext, role);
    }

    /*
     * A default module directory is automatically set when the SSL context was
     * created, but you can override in the config file.
     */

    moduleDir = Ns_ConfigGetValue(path, "moduledir");
    if (moduleDir != NULL) {
        NsOpenSSLContextModuleDirSet(server, sslcontext, moduleDir);
    }

    /*
     * SSL clients don't require certificates, but SSL servers do. If certfile
     * or keyfile are NULL, are not found, or are not accessible, we'll fail
     * later when we try to instantiate the SSL context.
     */

    certFile = Ns_ConfigGetValue(path, "certfile");
    if (certFile != NULL) {
        NsOpenSSLContextCertFileSet(server, sslcontext, certFile);
    }
    keyFile = Ns_ConfigGetValue(path, "keyfile");
    if (keyFile != NULL) {
        NsOpenSSLContextKeyFileSet(server, sslcontext, keyFile);
    }

    /*
     * The default protocols and ciphersuites are good for general use.
     */

    protocols = Ns_ConfigGetValue(path, "protocols");
    if (protocols != NULL) {
        NsOpenSSLContextProtocolsSet(server, sslcontext, protocols);
    }
    cipherSuite = Ns_ConfigGetValue(path, "ciphersuite");
    if (cipherSuite != NULL) {
        NsOpenSSLContextCipherSuiteSet(server, sslcontext, cipherSuite);
    }

    /*
     * The CA file/dir isn't necessary unless you actually do cert
     * verification. The CA file is simply a bunch of PEM-format CA
     * certificates concatenated together.
     */

    caFile = Ns_ConfigGetValue(path, "cafile");
    if (caFile != NULL) {
        NsOpenSSLContextCAFileSet(server, sslcontext, caFile);
    }
    caDir = Ns_ConfigGetValue(path, "cadir");
    if (caDir != NULL) {
        NsOpenSSLContextCADirSet(server, sslcontext, caDir);
    }

    /*
     * Peer verification will cause the server to request a client certificate.
     * If you aren't sure whether to turn it on or not, leave it off!
     */

    if (Ns_ConfigGetBool(path, "peerverify", &peerVerify) == NS_TRUE) {
        NsOpenSSLContextPeerVerifySet(server, sslcontext, peerVerify);
    }

    /*
     * A certificate may be at the bottom of a chain. Verify depth determines
     * how many levels down from the root cert you're willing to trust..
     */

    if (Ns_ConfigGetInt(path, "peerverifydepth", &peerVerifyDepth) == NS_TRUE) {
        NsOpenSSLContextPeerVerifyDepthSet(server, sslcontext, peerVerifyDepth);
    }

    /*
     * Session caching defaults to on, and should always be on if you
     * have web browsers connecting. Some versions of MSIE and Netscape will
     * fail if you don't have session caching on.
     */

    Ns_ConfigGetBool(path, "sessioncache", &sessionCache);
    if (sessionCache == NS_TRUE) {
        NsOpenSSLContextSessionCacheSet(server, sslcontext, sessionCache);
    }
    if (Ns_ConfigGetInt(path, "sessioncachesize", &sessionCacheSize) == NS_TRUE) {
        NsOpenSSLContextSessionCacheSizeSet(server, sslcontext, sessionCacheSize);
    }
    if (Ns_ConfigGetInt(path, "sessioncachetimeout", &sessionCacheTimeout) == NS_TRUE) {
        NsOpenSSLContextSessionCacheTimeoutSet(server, sslcontext, sessionCacheTimeout);
    }

    /*
     * Trace SSL handshake.
     */

    Ns_ConfigGetBool(path, "trace", &trace);
    if (trace == NS_TRUE) {
        NsOpenSSLContextTraceSet(server, sslcontext, 1);
    } else {
        NsOpenSSLContextTraceSet(server, sslcontext, 0);
    }

    return sslcontext;
}


/*
 *----------------------------------------------------------------------
 *
 * ServerShutdown --
 *
 *      Cleanup function to run at server shutdown.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static void
ServerShutdown(void *arg)
{
    char *server = (char *) arg;

    Ns_Log(Notice, "Shutdown called for server %s", server);

    /*
     *   for each vserver.driver
     *       for each vserver.driver.conn
     *           close, free
     *       endfor
     *       free vserver.driver
     *   endfor
     *   free vserver
     */

    return;
}


/*
 *----------------------------------------------------------------------
 *
 * InitOpenSSL --
 *
 *       Initialize the OpenSSL library.
 *
 * Results:
 *       NS_OK
 *
 * Side effects:
 *       Sets OpenSSL threading callbacks, seeds the pseudo random number
 *       generator, initializes SSL session caching id generation.
 *
 *----------------------------------------------------------------------
 */

static int
InitOpenSSL(void)
{
    int        i         = 0;
    int        seedcnt   = 0;
    size_t     num_locks = 0;
    char      *lockName  = NULL;
    Ns_DString ds;

    Ns_DStringInit(&ds);

    /*
     * Initialize OpenSSL callbacks
     */

    if (CRYPTO_set_mem_functions(ns_malloc, ns_realloc, ns_free) == 0)
        Ns_Log(Warning, "%s: OpenSSL memory callbacks failed in InitOpenSSL",
                MODULE);
    num_locks = CRYPTO_num_locks();
    locks = ns_calloc(num_locks, sizeof(*locks));
    for (i = 0; i < num_locks; i++) {
	Ns_DStringPrintf(&ds, "crypto:%d", i);
	lockName = Ns_DStringExport(&ds);
        Ns_MutexSetName2(locks + i, MODULE_SHORT, lockName);
	Ns_DStringTrunc(&ds, 0);
	ns_free(lockName);
	lockName = NULL;
    }
    Ns_DStringFree(&ds);
    CRYPTO_set_locking_callback(ThreadLockCallback);
    CRYPTO_set_id_callback(ThreadIdCallback);

    /*
     * Initialize the OpenSSL library itself
     */

    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();
    SSL_library_init();
    X509V3_add_standard_extensions();

    /*
     * Seed the OpenSSL Pseudo-Random Number Generator.
     */

    while (! RAND_status() && seedcnt < 3) {
        seedcnt++;
        Ns_Log(Notice, "%s: Seeding OpenSSL's PRNG", MODULE);
        SeedPRNG();
    }
    if (! RAND_status()) {
        Ns_Log(Warning, "%s: PRNG fails to have enough entropy after %d tries", 
                MODULE, seedcnt);
    }

    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * SeedPRNG --
 *
 *       Seed OpenSSL's PRNG. OpenSSL will seed the PRNG transparently if
 *       /dev/urandom is available.
 *
 * Results:
 *       NS_TRUE or NS_FALSE.
 *
 * Side effects:
 *       An NS_FALSE will result in the connection failing. This function
 *       might be called at any time by the temporary key generating
 *       function if the PRNG is not sufficiently entropinous (yes, I
 *       made that word up).
 *       
 *
 *----------------------------------------------------------------------
 */

static int
SeedPRNG(void)
{
    double *buf_ptr       = NULL;
    double *bufoffset_ptr = NULL;
    char   *path          = NULL;
    char   *randomFile    = NULL;
    size_t  size          = 0;
    int     i             = 0;
    int     seedBytes     = 0;
    int     readBytes     = 0;
    int     maxBytes      = 0;

    if (RAND_status()) {
        return NS_TRUE;
    } 
    path = Ns_ConfigGetPath(NULL, MODULE, NULL);
    if (Ns_ConfigGetInt(path, "seedbytes", &seedBytes) == NS_FALSE) {
        seedBytes = DEFAULT_SEEDBYTES;
    }
    if (Ns_ConfigGetInt(path, "maxbytes", &maxBytes) == NS_FALSE) {
        maxBytes = DEFAULT_MAXBYTES;
    }

    /*
     * Try to use the file specified by the user. If PRNG fails to seed here,
     * you might try increasing the seedBytes parameter in nsd.tcl.
     */

    randomFile = Ns_ConfigGetValue(path, "randomfile");
    if (randomFile != NULL && access(randomFile, F_OK) == 0) {
        if ((readBytes = RAND_load_file(randomFile, maxBytes))) {
            Ns_Log(Notice, "%s: Obtained %d random bytes from %s",
                    MODULE, readBytes, randomFile);
        } else {
            Ns_Log(Warning, "%s: Unable to retrieve any random data from %s",
                    MODULE, randomFile);
        }
    } else {
        Ns_Log(Warning, "%s: No randomFile set and/or found", MODULE);
    }
    if (RAND_status()) {
        return NS_TRUE;
    }

    /*
     * Use Ns_DRand(), passing it seedBytes as the second argument to RAND_add.
     */

    size = sizeof(double) * seedBytes;
    buf_ptr = Ns_Malloc(size);
    bufoffset_ptr = buf_ptr;
    for (i = 0; i < seedBytes; i++) {
        *bufoffset_ptr = Ns_DRand();
        bufoffset_ptr++;
    }
    RAND_add(buf_ptr, seedBytes, (double) seedBytes);
    ns_free(buf_ptr);
    if (!RAND_status()) {
        Ns_Log(Warning, "%s: failed to seed PRNG", MODULE);
        return NS_FALSE;
    }

    return NS_TRUE;
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadLockCallback --
 *
 *      Lock or unlock a mutex for OpenSSL.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static void
ThreadLockCallback(int mode, int n, const char *file, int line)
{
    if (mode & CRYPTO_LOCK) {
        Ns_MutexLock(locks + n);
    } else {
        Ns_MutexUnlock(locks + n);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadIdCallback --
 *
 *      Return this thread's id for OpenSSL.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static unsigned long
ThreadIdCallback(void)
{
    return (unsigned long) Ns_ThreadId();
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadDynlockCreateCallback --
 *
 *      Create a dynamically-allocated mutex for OpenSSL.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static struct CRYPTO_dynlock_value *
ThreadDynlockCreateCallback(char *file, int line)
{
    Ns_Mutex   *lock = NULL;
    Ns_DString  ds;

    Ns_DStringInit(&ds);
    lock = ns_calloc(1, sizeof(*lock));
    Ns_DStringVarAppend(&ds, "openssl: ", file, ": ");
    Ns_DStringPrintf(&ds, "%d", line);
    Ns_MutexSetName2(lock, MODULE, Ns_DStringValue(&ds));

    return (struct CRYPTO_dynlock_value *) lock;
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadDynlockLockCallback --
 *
 *      Lock or unlock a dynamically-allocated mutex for OpenSSL.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static void
ThreadDynlockLockCallback(int mode, struct CRYPTO_dynlock_value *dynlock,
        const char *file, int line)
{
    if (mode & CRYPTO_LOCK) {
        Ns_MutexLock((Ns_Mutex *) dynlock);
    } else {
        Ns_MutexUnlock((Ns_Mutex *) dynlock);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadDynlockDestroyCallback --
 *
 *      Destroy a dynamically-allocated mutex for OpenSSL.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

static void
ThreadDynlockDestroyCallback(struct CRYPTO_dynlock_value *dynlock,
        const char *file, int line)
{
    Ns_MutexDestroy((Ns_Mutex *) dynlock);
}


/*
 *----------------------------------------------------------------------
 *
 * LoadSSLDrivers --
 *
 *       Load the SSL drivers for a virtual server.
 *
 * Results:
 *       NS_OK or NS_ERROR
 *
 * Side effects:
 *       Registers driver with AOLserver core.
 *
 *----------------------------------------------------------------------
 */

static void
LoadSSLDrivers(char *server)
{
    NsOpenSSLContext *sslcontext     = NULL;
    NsOpenSSLDriver  *ssldriver      = NULL;
    Ns_Set           *ssldrivers     = NULL;
    char             *path           = NULL;
    char             *name           = NULL; 
    char             *sslcontextname = NULL;
    int               i              = 0;

    path = Ns_ConfigGetPath(server, MODULE, "ssldrivers", NULL);
    ssldrivers = Ns_ConfigGetSection(path);
    if (ssldrivers == NULL) {
        Ns_Log(Notice, "%s (%s): no SSL drivers defined for this server", 
                MODULE, server);
        return;
    }
    for (i = 0; i < Ns_SetSize(ssldrivers); ++i) {
        name = Ns_SetKey(ssldrivers, i);
        Ns_Log(Notice, "%s (%s): loading '%s' SSL driver", MODULE, server, name);
        path = Ns_ConfigGetPath(server, MODULE, "ssldriver", name, NULL);
        if (path == NULL) {
            Ns_Log(Error, "%s (%s): SSL driver '%s' not defined in configuration file",
                    MODULE, server, name);
            continue;
        }
        sslcontextname = Ns_ConfigGetValue(path, "sslcontext");
        if (sslcontextname == NULL) {
            Ns_Log(Error, "%s (%s): 'sslcontext' parameter not defined for driver '%s'",
                    MODULE, server, name);
            continue;
        }
        sslcontext = Ns_OpenSSLServerSSLContextGet(server, sslcontextname);
        if (sslcontext == NULL) {
            Ns_Log(Error, "%s (%s): SSL context '%s' needed by driver '%s' not found",
                    MODULE, server, sslcontextname, name);
            continue;
        }

        /*
         * Create the driver.
         */

        ssldriver = ns_calloc(1, sizeof(NsOpenSSLDriver));
        ssldriver->server     = server;
        ssldriver->sslcontext = sslcontext;
        ssldriver->name       = name;
        ssldriver->path       = path;
        ssldriver->refcnt     = 0;
        if (!Ns_ConfigGetInt(path, "port", &ssldriver->port)) {
            ssldriver->port = 443;
        }

        /*
         * Crank up the driver
         */

        if (InitSSLDriver(server, ssldriver) != NS_OK) { 

        }
    }
}


/*
 *----------------------------------------------------------------------
 *
 * InitSSLDriver --
 *
 *       Initialize an SSL driver.
 *
 * Results:
 *       NS_OK or NS_ERROR
 *
 * Side effects:
 *       Registers driver with AOLserver core.
 *
 *----------------------------------------------------------------------
 */

static int
InitSSLDriver(char *server, NsOpenSSLDriver *ssldriver)
{
    Ns_DriverInitData  init;
    Server            *thisServer = NULL;
    Tcl_HashEntry     *hPtr       = NULL;
    int                new        = 0;

    /*
     * Register the driver with AOLserver.
     */

    init.version = NS_DRIVER_VERSION_1;
    init.name    = MODULE;
    init.proc    = OpenSSLProc;
    init.opts    = NS_DRIVER_SSL;
    init.arg     = ssldriver;
    init.path    = ssldriver->path;

    if (Ns_DriverInit(server, MODULE, &init) != NS_ERROR) {
        return NS_ERROR;
    }

    /*
     * Add the driver to the virtual server's state info.
     */

    thisServer = NsOpenSSLServerGet(server);
    Ns_MutexLock(&thisServer->lock);
    hPtr = Tcl_CreateHashEntry(&thisServer->ssldrivers, ssldriver->name, &new);
    if (new) {
        Tcl_SetHashValue(hPtr, ssldriver);
    } else {
        Ns_Log(Error, "%s (%s): duplicate SSL driver name: %s",
                MODULE, server, ssldriver->name);
        return NS_ERROR;
    }
    Ns_MutexUnlock(&thisServer->lock);

    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * DestroySSLDriver --
 *
 *      Destroy an SSL driver.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

#if 0
static void
DestroySSLDriver(NsOpenSSLDriver *ssldriver)
{
    NsOpenSSLConn *sslconn;

    if (ssldriver == NULL) {
        return;
    }

    Ns_Log(Notice, "%s (%s): shutting down driver '%s'", MODULE, 
            ssldriver->server, ssldriver->name);

    /*
     * Destroy connections that are still tied to this driver. We need to lock
     * the driver struct, set a flag that denotes it as no longer usable so new
     * conns that come in before we've free'd it will be refused.
     */

    /* XXX lock */
    if (ssldriver->refcnt > 0) {
        while ((sslconn = ssldriver->firstFreeConn) != NULL) {
            ssldriver->firstFreeConn = sslconn->next;
            /* XXX doesn't this need to have it's contents free'd? */
            ns_free(sslconn);
        }
    }

    Ns_MutexDestroy(&ssldriver->lock);

    /* XXX should an SSL context be deallocated when it's refcnt reaches 0 ??? */
    if (ssldriver->sslcontext != NULL) 
        ssldriver->sslcontext->refcnt--;

    ns_free(ssldriver);

    /*
     * Remove driver from server state linked list.
     */

    return;
}
#endif


/*            
 *----------------------------------------------------------------------
 *
 * OpenSSLProc --
 *
 *      SSL driver callback proc.  This driver performs the necessary
 *      handshake and encryption of SSL.
 *
 * Results:   
 *      For close, always 0.  For keep, 0 if connection could be
 *      properly flushed, -1 otherwise.  For send and recv, # of bytes
 *      processed or -1 on error.
 *
 * Side effects:
 *      None. 
 *            
 *----------------------------------------------------------------------
 */

static int
OpenSSLProc(Ns_DriverCmd cmd, Ns_Sock *sock, struct iovec *bufs, int nbufs)
{
    NsOpenSSLDriver *driver = (NsOpenSSLDriver *) sock->driver->arg;
    NsOpenSSLConn   *conn   = (NsOpenSSLConn *)   sock->arg;
    int n = -1, total, op;

    switch (cmd) {
        case DriverRecv:
        case DriverSend:
            if (conn == NULL) {
                /* 
                 * If first connection, wrap SSL around the socket.
                 */

                conn = NsOpenSSLConnCreate(sock->sock, driver->sslcontext);
                conn->refcnt++;
                conn->peerport  = driver->port;
                conn->recvwait  = sock->driver->recvwait;
                conn->sendwait  = sock->driver->sendwait;
                sock->arg       = (void *) conn;
            }

            /* 
             * Process each buffer one at a time.
             */

            op = (cmd == DriverSend) ? NSOPENSSL_SEND : NSOPENSSL_RECV;
            total = 0;
            do {
                n = NsOpenSSLConnOp(conn->ssl, bufs->iov_base,
                        (int) bufs->iov_len, op);
                if (n > 0) {
                    total += n;
                }
                ++bufs;
            } while (n > 0 && --nbufs > 0);
            if (n > 0) {
                n = total;
            }
            break;

        case DriverKeep:
            if (conn != NULL && NsOpenSSLConnFlush(conn) == NS_OK) {
                n = 0;
            } else {
                n = -1;
            }
            break;

        case DriverClose:
            if (conn != NULL) {
                (void) NsOpenSSLConnFlush(conn);
                NsOpenSSLConnDestroy(conn);
                sock->arg = NULL;
            }
            n = 0;
            break;

        default:
            Ns_Log(Error, "%s (%s): Unsupported driver command '%d'", 
                    MODULE, driver->server, cmd);
            n = -1;
            break;
    }
    return n;
}

