/* ====================================================================
 * Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 Ben Laurie.
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by Ben Laurie
 *    for use in the Apache-SSL HTTP server project."
 *
 * 4. The name "Apache-SSL Server" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Ben Laurie
 *    for use in the Apache-SSL HTTP server project."
 *
 * THIS SOFTWARE IS PROVIDED BY BEN LAURIE ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL BEN LAURIE OR
 * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of patches to the Apache HTTP server interfacing it
 * to SSLeay.
 * For more information on Apache-SSL see http://www.apache-ssl.org.
 *
 * For more information on Apache see http://www.apache.org.
 *
 * For more information on SSLeay see http://www.psy.uq.oz.au/~ftp/Crypto/.
 *
 * Ben Laurie <ben@algroup.co.uk>.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>
#include <openssl/rand.h>

#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "http_main.h"


/* Note that APACHE_SSL_EXPORT_CERTS is defined in buff.h */

/* Temp */
#define RENEG		FALSE
#define DEBUG_RENEG	FALSE
#define DEBUG_SSL_STATE FALSE
#define ENGINE_SUPPORT	FALSE

#if ENGINE_SUPPORT
#include <openssl/engine.h>
#endif

#define USE_OLD_ENVIRONMENT	FALSE	/* Set to TRUE to use the old SSL_{CLIENT,SERVER}_I* instead of SSL_{CLIENT,SERVER}_I_* */

#define SSL_IS_OPTIONAL		TRUE	/* Set to TRUE if you want to allow SSL to be disabled on a per-request basis (useful for subrequests) */

#define USE_EXTENDED_ENVIRONMENT      TRUE    /* Set to TRUE to use the SSL_{CLIENT,SERVER}_{I,S}_DN* */

#if SSLEAY_VERSION_NUMBER >= 0x0800
/* Do session caching - only available with SSLeay 0.8.x */
# define CACHE_SESSIONS		TRUE
#endif

#if SSL_IS_OPTIONAL
# define OPTIONAL_SSL	(!r->connection->client->ssl)
#else
# define OPTIONAL_SSL	FALSE
#endif

#if SSLEAY_VERSION_NUMBER < 0x0800
# error "SSLeay versions below 0.8.x are no longer supported"
/* But note, they nearly are, if anyone cares */
#endif

#if SSLEAY_VERSION_NUMBER < 0x0900 && APACHE_SSL_EXPORT_CERTS
# error "Can't export certificates with SSLeay before 0.9.0"
#endif

#if SSLEAY_VERSION_NUMBER < 0x0908000
# define DECRYPTFINAL	EVP_F_EVP_DECRYPTFINAL
#else
# define DECRYPTFINAL	EVP_F_EVP_DECRYPTFINAL_EX
#endif

#define FREE_SESSION	TRUE	/* Don't set this FALSE! */

/* define this to debug the verification stuff */
#define DEBUG_VERIFY	TRUE

#if CACHE_SESSIONS
# include "gcache.h"
static const char *s_szCacheServerPort;
static const char *s_szCacheServerDir;
static char *s_szCacheServerPath;
static char *s_szCacheServerArg1;
static char *s_szCacheServerArg2;
static char *s_szSplashUnicast;

# define DEBUG_SESSIONS	FALSE

#endif

#if ENGINE_SUPPORT
static const char *s_szEngineID = NULL;
#endif

#if APACHE_SSL_EXPORT_CERTS && !CACHE_SESSIONS
# error "CACHE_SESSIONS must be TRUE if APACHE_SSL_EXPORT_CERTS is"
#endif

#if SSLEAY_VERSION_NUMBER < 0x900
/* correct for a typo */
# define SSL3_TXT_ADH_DES_192_CBC_SHA SSL3_TXT_ADH_DES_196_CBC_SHA
#endif

#if SSLEAY_VERSION_NUMBER < 0x600
# define ERR_print_errors_fp	ERR_print_errors
#endif

#if SSLEAY_VERSION_NUMBER < 0x0800
# define X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY \
		VERIFY_ERR_UNABLE_TO_GET_ISSUER
# define X509_V_OK VERIFY_OK
# define SSL_CTX_set_default_verify_paths(ctx)	\
		SSL_set_default_verify_paths(ctx)
# define SSL_CTX_load_verify_locations(ctx,cafile,capath) \
		 SSL_load_verify_locations(ctx,cafile,capath)
# define X509_verify_cert_error_string(error) X509_cert_verify_error_string(error)
#endif

#if OPENSSL_VERSION_NUMBER >= 0x00904000L
# define localPEM_read_RSAPrivateKey(f,k,x) PEM_read_RSAPrivateKey(f,k,x,NULL)
# define localPEM_read_X509(f,c,x)	    PEM_read_X509(f,c,x,NULL)
#else
# define localPEM_read_RSAPrivateKey(f,k,x) PEM_read_RSAPrivateKey(f,k,x)
# define localPEM_read_X509(f,c,x)	    PEM_read_X509(f,c,x)
#endif

#define ENV_CERT_CHAIN		"SSL_CLIENT_CERT_CHAIN_"

#define UNSET	-1

/* Offsets for data stored in SSL structures */
#define EX_DATA_CONN_REC	0
#define EX_DATA_SERVER_REC	1
#if RENEG
# define EX_DATA_DIR_CONFIG	2
#endif

typedef enum
    {
    VERIFY_UNSET=-1,
    VERIFY_NONE=0,
    VERIFY_OPTIONAL=1,
    VERIFY_REQUIRE=2,
    VERIFY_OPTIONAL_NO_CA=3
    } VerifyType;

typedef enum
    {
    SSLREQ_UNSET=-1,
    SSLREQ_REQUIRED=0,
    SSLREQ_DENIED=1
    } RequireType;

typedef struct
    {
    BOOL bDisabled;

    char *szCertificateFile;
    char *szKeyFile;
    char *szCACertificatePath;
    char *szCACertificateFile;
    char *szReqCiphers;
    char *szDNCheckFile;
#if !RENEG
    int nVerifyDepth;
    VerifyType nVerifyClient;
#endif

    X509 *px509Certificate;
    RSA *prsaKey;

    SSL_CTX *pSSLCtx;

    BOOL bFakeBasicAuth;
    BOOL bNoCAList;
    BOOL bUseCRL;
    BOOL bCRLCheckAll;
    BOOL bNoV2;

    char *szOnRevocationSetEnv;
    char *szOnCRLExpirySetEnv;
    char *szOnNoCRLSetEnv;

    time_t tSessionCacheTimeout;

    array_header *aRandomFiles;
    } SSLConfigRec;

typedef struct
    {
    /* 1) If cipher is banned, refuse */
    /* 2) If RequiredCiphers is NULL, accept */
    /* 3) If the cipher isn't required, refuse */

    table *tbRequiredCiphers;
    table *tbBannedCiphers;
    RequireType nSSLRequired;
#if APACHE_SSL_EXPORT_CERTS
    BOOL bExportCerts;
#endif
#if RENEG
    int nVerifyDepth;
    VerifyType nVerifyClient;
#endif
    } SSLDirConfigRec;

MODULE_VAR_EXPORT module apache_ssl_module;

static pool *s_pPermanentPool;

static const char six2pr[64+1]=
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static void uuencoden(unsigned char *szTo,const unsigned char *szFrom,
                     int nLength,BOOL bPad)
    {
    const unsigned char *s;
    int nPad=0;

    for(s=(const unsigned char *)szFrom ; nLength > 0 ; s+=3)
	{
	*szTo++=six2pr[s[0] >> 2];
	*szTo++=six2pr[(s[0] << 4 | s[1] >> 4)&0x3f];
	if(!--nLength)
	    {
	    nPad=2;
	    break;
	    }
	*szTo++=six2pr[(s[1] << 2 | s[2] >> 6)&0x3f];
	if(!--nLength)
	    {
	    nPad=1;
	    break;
	    }
	*szTo++=six2pr[s[2]&0x3f];
	--nLength;
	}
    while(bPad && nPad--)
	*szTo++='=';
    *szTo++='\0';
    }

static void uuencode(char *szTo,const char *szFrom,BOOL bPad)
    {
    uuencoden((unsigned char *)szTo,(const unsigned char *)szFrom,
	      strlen(szFrom),bPad);
    }

static void SSLLogError(server_rec *s,int nLevel,const char *szErr)
    {
    unsigned long l;
    char buf[MAX_STRING_LEN];

    if(szErr)
	ap_log_error(APLOG_MARK,nLevel|APLOG_NOERRNO,s,"%s",szErr);
    while((l=ERR_get_error()))
	{
	ERR_error_string(l,buf);
	ap_log_error(APLOG_MARK,nLevel|APLOG_NOERRNO,s,"%s",buf);
	}
    }

/* for debugging */
static void SSLLogErrorToFile(FILE *f)
    {
    unsigned long l;
    char buf[MAX_STRING_LEN];

    /* Print out error messages */

    while((l=ERR_get_error()))
	{
	ERR_error_string(l,buf);
	fputs(buf,f);
	fputc('\n',f);
	}
    }

#if DEBUG_SSL_STATE

static void InfoCallback(SSL *s,int where,int ret)
    {
    char *str;
    int w;
    server_rec *pServer=SSL_get_ex_data(s,EX_DATA_SERVER_REC);

    w=where&~SSL_ST_MASK;

    if(w & SSL_ST_CONNECT)
	str="SSL_connect";
    else if(w & SSL_ST_ACCEPT)
	str="SSL_accept";
    else
	str="undefined";

    if(where & SSL_CB_LOOP)
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,"%s:%s (%s)",
		     str,SSL_state_string_long(s),SSL_get_cipher(s));
    else if(where & SSL_CB_ALERT)
	{
	str=(where & SSL_CB_READ)?"read":"write";
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		     "SSL3 alert %s:%s:%s",
		     str,
		     SSL_alert_type_string_long(ret),
		     SSL_alert_desc_string_long(ret));
	}
    else if(where & SSL_CB_EXIT)
	{
	if(ret == 0)
	    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
			 "%s:failed in %s",
			 str,SSL_state_string_long(s));
	else if (ret < 0)
	    {
	    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
			 "%s:error in %s",
			 str,SSL_state_string_long(s));
	    }
	}
    }

#endif /* DEBUG_SSL_STATE */

typedef enum
    {
    RAND_EGD,
    RAND_FILE
    } RFType;

typedef struct
    {
    RFType eType;
    const char *szFile;
    int nBytes;
    } RandomFile;

static void ReadRandomFiles(server_rec *pServer,RandomFile *pRF,int nRF)
    {
    int n;

    for(n=0 ; n < nRF ; ++n,++pRF)
	{
	int r=pRF->eType == RAND_EGD
	  ? RAND_egd_bytes(pRF->szFile,pRF->nBytes)
	  : RAND_load_file(pRF->szFile,pRF->nBytes);
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		     "Random input %s(%d) -> %d",pRF->szFile,pRF->nBytes,r);
	}
    }

#if APACHE_SSL_EXPORT_CERTS
/* szEnv must have been pool allocated, or be static */
static void AddCertToEnv(pool *p,table *e,char *szEnv,X509 *xs)
    {
    int n;
    unsigned char *s,*d;

    n=i2d_X509(xs,NULL);
    d=s=ap_palloc(p,n);
    i2d_X509(xs,&d);
    d=ap_palloc(p,(n*4)/3+2+2);
    uuencoden(d,s,n,TRUE);
    ap_table_setn(e,szEnv,(char *)d);
    }
#endif

#if SSLEAY_VERSION_NUMBER >= 0x0800
static int ApacheSSLVerifyCallback(int ok,X509_STORE_CTX *ctx)
    {
    X509 *xs=X509_STORE_CTX_get_current_cert(ctx);
    int depth=X509_STORE_CTX_get_error_depth(ctx);
    int error=X509_STORE_CTX_get_error(ctx);
    SSL *pSSL=X509_STORE_CTX_get_ex_data(ctx,
					 SSL_get_ex_data_X509_STORE_CTX_idx());
    conn_rec *pCurrentConnection=SSL_get_ex_data(pSSL,EX_DATA_CONN_REC);
#else
static int ApacheSSLVerifyCallback(int ok,X509 *xs,X509 *xi,int depth,
				   int error)
    {
#endif

#if RENEG
    SSLDirConfigRec *pConfig=SSL_get_ex_data(pSSL,EX_DATA_DIR_CONFIG);
    SSLConfigRec *pSConfig=
      ap_get_module_config(pCurrentConnection->server->module_config,
			   &apache_ssl_module);
#else
    SSLConfigRec *pConfig=
      ap_get_module_config(pCurrentConnection->server->module_config,
			   &apache_ssl_module);
    SSLConfigRec *pSConfig=pConfig;
#endif
    server_rec *pServer=pCurrentConnection->server;

#if DEBUG_VERIFY
      {
      char *s,*s2;
      char s3[512];
      BIO *bio;

      s=X509_NAME_oneline(X509_get_subject_name(xs),NULL,0);
      s2=X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0);
      bio=BIO_new(BIO_s_mem());
      i2a_ASN1_INTEGER(bio,X509_get_serialNumber(xs));
      BIO_gets(bio,s3,sizeof s3);
      BIO_free(bio);

      if(s == NULL)
	  {
	  SSLLogError(pServer,APLOG_ERR,"can't get subject name");
	  return(0);
	  }

      ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		   "depth=%d serial=%s subject=%s issuer=%s",depth,s3,s,s2);
      free(s);
      }
#endif
    
#if APACHE_SSL_KEEP_CERTS
    if(depth)
	{
	char *t;
	pool *p=pCurrentConnection->client->pool;

	if(!pCurrentConnection->client->tableCertChain)
	    pCurrentConnection->client->tableCertChain=ap_make_table(p,5);

	t=ap_psprintf(p,ENV_CERT_CHAIN "%d",depth);
	AddCertToEnv(p,pCurrentConnection->client->tableCertChain,t,xs);
	}
#endif

    if(error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
       || error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
       || error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
       || error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
#if OPENSSL_VERSION_NUMBER >= 0x00905000L
       || error == X509_V_ERR_CERT_UNTRUSTED
#endif
       )
	{
	char *s;

	if(pConfig->nVerifyClient == VERIFY_OPTIONAL_NO_CA)
	    {
	    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
			 "no issuer, returning OK");
	    return TRUE;
	    }
#if SSLEAY_VERSION_NUMBER < 0x0800
	s=X509_NAME_oneline(X509_get_issuer_name(xs));
#else
	s=X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0);
#endif
	if(s == NULL)
	    {
	    SSLLogError(pServer,APLOG_ERR,"verify error");
	    return FALSE;
	    }
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,"issuer=%s",
		     s);
	free(s);
	}
    if(error == X509_V_ERR_CERT_REVOKED && pSConfig->szOnRevocationSetEnv)
	{
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		     "certificate revoked, setting env var and returning OK");
	pCurrentConnection->client->bRevoked=TRUE;
	return TRUE;
	}
    if(error == X509_V_ERR_CRL_HAS_EXPIRED && pSConfig->szOnCRLExpirySetEnv)
	{
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		     "CRL expired, setting env var and returning OK");
	pCurrentConnection->client->bCRLExpired=TRUE;
	return TRUE;
	}
    if(error == X509_V_ERR_UNABLE_TO_GET_CRL && pSConfig->szOnNoCRLSetEnv)
	{
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		     "No CRL, setting env var and returning OK");
	pCurrentConnection->client->bNoCRL=TRUE;
	return TRUE;
	}


    if(!ok)
	{
	ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,pServer,
		      "verify error:num=%d:%s",error,
		      X509_verify_cert_error_string(error));
	SSLLogError(pCurrentConnection->server,APLOG_ERR,NULL);
	pCurrentConnection->client->szClientX509=NULL;
	}
    if(depth >= pConfig->nVerifyDepth)
	{
	ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,
		     pServer,"Verify depth exceeded");
	ok=0;
	}
    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,pServer,
		 "verify error=%d return=%d",error,ok);
    return ok;
    }

#if RENEG
static int VerifyFlags(SSLDirConfigRec *pConfig)
#else
static int VerifyFlags(SSLConfigRec *pConfig)
#endif
    {
    int nVerify=0;

    switch(pConfig->nVerifyClient)
	{
    case VERIFY_REQUIRE:
	nVerify|=SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
	break;

    case VERIFY_OPTIONAL:
    case VERIFY_OPTIONAL_NO_CA:
	nVerify|=SSL_VERIFY_PEER;
	break;

    default: break; /* avoid warnings from gcc */
	}

    return nVerify;
    }

static int SSLCheckCipher(request_rec *r)
    {
    const char *cipher;
    SSLDirConfigRec *rec=(SSLDirConfigRec *)
      ap_get_module_config(r->per_dir_config,&apache_ssl_module);
    SSLConfigRec *pConfig=(SSLConfigRec *)
      ap_get_module_config(r->server->module_config,&apache_ssl_module);

    if(rec->nSSLRequired == SSLREQ_DENIED && r->connection->client->ssl)
	{
	ap_log_reason("SSL denied",r->filename,r);
	return FORBIDDEN;
	}

    if(rec->nSSLRequired == SSLREQ_REQUIRED && !r->connection->client->ssl)
	{
	ap_log_reason("SSL required",r->filename,r);
	return FORBIDDEN;
	}

    /* Check to see if SSL is on */
    if(pConfig->bDisabled || OPTIONAL_SSL)
	return DECLINED;

    if (r->connection->client->ssl == NULL) return DECLINED;

#if RENEG
# if DEBUG_RENEG
    fprintf(stderr,"Renegotiated for %s (%x)!\n",r->filename,VerifyFlags(rec));
# endif
    if(1)
	{
	SSL_set_ex_data(r->connection->client->ssl,EX_DATA_DIR_CONFIG,rec);
	SSL_set_verify(r->connection->client->ssl,VerifyFlags(rec),
		       ApacheSSLVerifyCallback);
	/* Set the context. This is probably not optimal (but nor is the whole,
	   of renegotiation. But it certainly should avoid inappropriate
	   session reuse. */
	SSL_set_session_id_context(r->connection->client->ssl,
				   (unsigned char *)&rec,sizeof rec);
	SSL_renegotiate(r->connection->client->ssl);
	SSL_do_handshake(r->connection->client->ssl);

	r->connection->client->ssl->state=SSL_ST_ACCEPT;
	SSL_do_handshake(r->connection->client->ssl);
	}

    if(rec->nVerifyClient != VERIFY_NONE && rec->nVerifyClient != VERIFY_UNSET)
	{
	char *s;
	X509 *xs;
	xs=SSL_get_peer_certificate(r->connection->client->ssl);
	if(xs)
	    {
	    s=X509_NAME_oneline(X509_get_subject_name(xs),NULL,0);
	    r->connection->client->szClientX509=ap_pstrdup(r->connection->pool,
							   s);
	    free(s);
	    }
	}

    if(rec->nVerifyClient == VERIFY_REQUIRE
       && !r->connection->client->szClientX509)
	{
	SSLLogError(r->connection->server,APLOG_ERR,"No client certificate");
	return FORBIDDEN;
	}
#endif

    cipher=SSL_get_cipher(r->connection->client->ssl);

    if(ap_table_get(rec->tbBannedCiphers,cipher))
	{
	char *buf;

	buf=ap_pstrcat(r->pool,"Cipher ",cipher," is forbidden",NULL);
	ap_log_reason(buf,r->filename,r);
	return FORBIDDEN;
	}

    if(ap_table_get(rec->tbRequiredCiphers,cipher))
	return OK;

    if(ap_is_empty_table(rec->tbRequiredCiphers))
	return OK;
    else
	{
	char *buf;
	
	buf=ap_pstrcat(r->pool,"Cipher ",cipher,
		       " is not on the permitted list",NULL);
	ap_log_reason(buf,r->filename,r);
	return FORBIDDEN;
	}
    }

static int CheckDN(request_rec *r)
    {
    SSLConfigRec *pConfig=ap_get_module_config(r->server->module_config,
					       &apache_ssl_module);
    FILE *f;
  
    if(OPTIONAL_SSL)
	return DECLINED;
    if(!pConfig->szDNCheckFile)
	return DECLINED;
    if(r->connection->user)
	return DECLINED;
    if(!r->connection->client->szClientX509)
	return DECLINED;

    f=fopen(pConfig->szDNCheckFile,"r");
    if(!f)
        {
	ap_log_error(APLOG_MARK,APLOG_CRIT,r->server,
		     "Can't open DN check file: %s",pConfig->szDNCheckFile);
	return FORBIDDEN;
	}

    for( ; ; )
        {
	char buf[MAX_STRING_LEN];
	int n;

	if(!fgets(buf,sizeof buf,f))
	    break;
	n=strlen(buf);
	if(n && buf[n-1] == '\n')
	    buf[n-1]='\0';

	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,r->server,
		     "Comparing '%s' to '%s'",buf,
		     r->connection->client->szClientX509);

	if(!strcmp(buf,r->connection->client->szClientX509))
	    {
	    fclose(f);
	    return OK;
	    }
	}
    fclose(f);

    ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,r->server,
		 "DN '%s' not found in %s",r->connection->client->szClientX509,
		 pConfig->szDNCheckFile);

    return FORBIDDEN;
    }

static int CheckAccess(request_rec *r)
    {
    int ret;

    ret=SSLCheckCipher(r);
    if(ret != OK)
        return ret;
    return CheckDN(r);
    }

static void StrUpper(char *s)
    {
    for( ; *s ; ++s)
	*s=toupper(*s);
    }

#if USE_EXTENDED_ENVIRONMENT
static void get_serial(pool *p,table *pEnv,char *name, X509 *xs)
{
    BIO *bio;
    char *result;
    int n;

    if ((bio = BIO_new(BIO_s_mem())) == NULL) return;
    i2a_ASN1_INTEGER(bio, X509_get_serialNumber(xs));
    n = BIO_pending(bio);
    result = ap_pcalloc(p, n+1);
    n = BIO_read(bio, result, n);
    result[n] = 0;
    ap_table_setn(pEnv,name, result);
    BIO_free(bio);
}

static void get_valid(pool *p, table *pEnv, char *name, ASN1_UTCTIME *tm)
{
    BIO *bio;
    char *result;
    int n;

    if ((bio = BIO_new(BIO_s_mem())) == NULL) return;
    ASN1_UTCTIME_print(bio, tm);
    n = BIO_pending(bio);
    result = ap_pcalloc(p, n+1);
    n = BIO_read(bio, result, n);
    result[n] = 0;
    ap_table_setn(pEnv, name, result);
    BIO_free(bio);
}

#endif

static void ExpandCert(pool *p,table *pEnv,char *szPrefix, char *szDN, char *szCert)
    {
    char buf[HUGE_STRING_LEN];
    char *s,*t;
    /* Expand a X509_oneline entry into it's base components and register
       them as environment variables. Needed if you want to pass certificate
       information to CGI's. The naming convention SHOULD be fairly compatible
       with CGI's written for stronghold's certificate info  - Q */
    /* FIXME - strtok() and strcspn() may cause problems on some systems - Q */

    ap_table_setn(pEnv,szDN,ap_pstrdup(p,szCert));
    
    ap_cpystrn(buf,szCert,sizeof buf);
    for(s=strtok(buf,"/") ; s != NULL ; s=strtok(NULL,"/"))
	{
	int n=strcspn(s,"=");
	s[n]='\0';
	StrUpper(s);
	t=ap_pstrcat(p,szPrefix,s,NULL);
	ap_table_setn(pEnv,t,ap_pstrdup(p,s+n+1));
	}
    }

static struct
    {
    char *szName;
    int nKeySize;
    int nSecretKeySize;
    } aCipherspecs[]=
    {
#if OPENSSL_VERSION_NUMBER >= 0x00907000L
    { TLS1_TXT_RSA_WITH_AES_128_SHA /*"AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_DH_DSS_WITH_AES_128_SHA /*"DH-DSS-AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_DH_RSA_WITH_AES_128_SHA /*"DH-RSA-AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_DHE_DSS_WITH_AES_128_SHA /*"DHE-DSS-AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_DHE_RSA_WITH_AES_128_SHA /*"DHE-RSA-AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_ADH_WITH_AES_128_SHA /*"ADH-AES128-SHA"*/, 128, 128 },
    { TLS1_TXT_RSA_WITH_AES_256_SHA /*"AES256-SHA"*/, 256, 256 },
    { TLS1_TXT_DH_DSS_WITH_AES_256_SHA /*"DH-DSS-AES256-SHA"*/, 256, 256 },
    { TLS1_TXT_DH_RSA_WITH_AES_256_SHA /*"DH-RSA-AES256-SHA"*/, 256, 256 },
    { TLS1_TXT_DHE_DSS_WITH_AES_256_SHA /*"DHE-DSS-AES256-SHA"*/, 256, 256 },
    { TLS1_TXT_DHE_RSA_WITH_AES_256_SHA /*"DHE-RSA-AES256-SHA"*/, 256, 256 },
    { TLS1_TXT_ADH_WITH_AES_256_SHA /*"ADH-AES256-SHA"*/, 256, 256 },
#endif
    { TLS1_TXT_RSA_EXPORT1024_WITH_RC4_56_MD5 /*"EXP1024-RC4-MD5"*/, 128, 56 },
    { TLS1_TXT_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5 /*"EXP1024-RC2-CBC-MD5"*/, 128, 56 },
    { TLS1_TXT_RSA_EXPORT1024_WITH_DES_CBC_SHA /*"EXP1024-DES-CBC-SHA"*/, 56, 56 },
    { TLS1_TXT_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA /*"EXP1024-DHE-DSS-DES-CBC-SHA"*/, 56, 56 },
    { TLS1_TXT_RSA_EXPORT1024_WITH_RC4_56_SHA /*"EXP1024-RC4-SHA"*/, 128, 56 },
    { TLS1_TXT_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA /*"EXP1024-DHE-DSS-RC4-SHA"*/, 128, 56 },
    { TLS1_TXT_DHE_DSS_WITH_RC4_128_SHA /*"DHE-DSS-RC4-SHA"*/, 128, 128 },
#if SSLEAY_VERSION_NUMBER >= 0x0900
    { SSL3_TXT_RSA_IDEA_128_SHA		/*"IDEA-CBC-SHA"*/,	128, 128 },
#endif
    { SSL3_TXT_RSA_NULL_MD5		/*"NULL-MD5"*/, 	0, 0 },
    { SSL3_TXT_RSA_NULL_SHA	 	/*"NULL-SHA"*/, 	0, 0 },
    { SSL3_TXT_RSA_RC4_40_MD5		/*"EXP-RC4-MD5"*/,	128, 40 },
    { SSL3_TXT_RSA_RC4_128_MD5		/*"RC4-MD5"*/,		128, 128 },
    { SSL3_TXT_RSA_RC4_128_SHA		/*"RC4-SHA"*/,		128, 128 },
    { SSL3_TXT_RSA_RC2_40_MD5		/*"EXP-RC2-CBC-MD5"*/,	128, 40 },
    { SSL3_TXT_RSA_IDEA_128_SHA		/*"IDEA-CBC-MD5"*/,	128, 128 },
    { SSL3_TXT_RSA_DES_40_CBC_SHA	/*"EXP-DES-CBC-SHA"*/,	56, 40 },
    { SSL3_TXT_RSA_DES_64_CBC_SHA	/*"DES-CBC-SHA"*/,	56, 56 },
    { SSL3_TXT_RSA_DES_192_CBC3_SHA	/*"DES-CBC3-SHA"*/,	168, 168 },
    { SSL3_TXT_DH_DSS_DES_40_CBC_SHA	/*"EXP-DH-DSS-DES-CBC-SHA"*/, 56, 40 },
    { SSL3_TXT_DH_DSS_DES_64_CBC_SHA	/*"DH-DSS-DES-CBC-SHA"*/, 56, 56 },
    { SSL3_TXT_DH_DSS_DES_192_CBC3_SHA 	/*"DH-DSS-DES-CBC3-SHA"*/, 168, 168 },
    { SSL3_TXT_DH_RSA_DES_40_CBC_SHA	/*"EXP-DH-RSA-DES-CBC-SHA"*/, 56, 40 },
    { SSL3_TXT_DH_RSA_DES_64_CBC_SHA	/*"DH-RSA-DES-CBC-SHA"*/, 56, 56 },
    { SSL3_TXT_DH_RSA_DES_192_CBC3_SHA 	/*"DH-RSA-DES-CBC3-SHA"*/, 168, 168 },
    { SSL3_TXT_EDH_DSS_DES_40_CBC_SHA	/*"EXP-EDH-DSS-DES-CBC-SHA"*/, 56, 40 },
    { SSL3_TXT_EDH_DSS_DES_64_CBC_SHA	/*"EDH-DSS-DES-CBC-SHA"*/, 56, 56 },
    { SSL3_TXT_EDH_DSS_DES_192_CBC3_SHA	/*"EDH-DSS-DES-CBC3-SHA"*/, 168, 168 },
    { SSL3_TXT_EDH_RSA_DES_40_CBC_SHA	/*"EXP-EDH-RSA-DES-CBC"*/, 56, 40 },
    { SSL3_TXT_EDH_RSA_DES_64_CBC_SHA	/*"EDH-RSA-DES-CBC-SHA"*/, 56, 56 },
    { SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA	/*"EDH-RSA-DES-CBC3-SHA"*/, 168, 168 },
    { SSL3_TXT_ADH_RC4_40_MD5		/*"EXP-ADH-RC4-MD5"*/,	128, 40 },
    { SSL3_TXT_ADH_RC4_128_MD5		/*"ADH-RC4-MD5"*/,	128, 128 },
    { SSL3_TXT_ADH_DES_40_CBC_SHA	/*"EXP-ADH-DES-CBC-SHA"*/, 128, 40 },
    { SSL3_TXT_ADH_DES_64_CBC_SHA	/*"ADH-DES-CBC-SHA"*/,	56, 56 },
    { SSL3_TXT_ADH_DES_192_CBC_SHA	/*"ADH-DES-CBC3-SHA"*/, 168, 168 },
    { SSL3_TXT_FZA_DMS_NULL_SHA		/*"FZA-NULL-SHA"*/,	0, 0 },
    { SSL3_TXT_FZA_DMS_FZA_SHA		/*"FZA-FZA-CBC-SHA"*/,	-1, -1 },
    { SSL3_TXT_FZA_DMS_RC4_SHA		/*"FZA-RC4-SHA"*/,	 128, 128 },
    { SSL2_TXT_DES_64_CFB64_WITH_MD5_1	/*"DES-CFB-M1"*/,	56, 56 },
    { SSL2_TXT_RC2_128_CBC_WITH_MD5	/*"RC2-CBC-MD5"*/,	128, 128 },
    { SSL2_TXT_DES_64_CBC_WITH_MD5	/*"DES-CBC-MD5"*/,	56, 56 },
    { SSL2_TXT_DES_192_EDE3_CBC_WITH_MD5/*"DES-CBC3-MD5"*/,	168, 168 },
    { SSL2_TXT_RC4_64_WITH_MD5		/*"RC4-64-MD5"*/,	64, 64 },
    { SSL2_TXT_NULL			/*"NULL"*/,		0, 0 },
    { NULL, 0, 0 }
    };

static void SSLAddCommonVars(request_rec *r)
    {
    table *e=r->subprocess_env;
    int keysize=0;
    int secretkeysize=0;
    char *t;
    const char *cipher;
    int n;
    X509 *xs;
#if APACHE_SSL_EXPORT_CERTS && !APACHE_SSL_KEEP_CERTS
    STACK *pCertStack;
#endif

    SSLConfigRec *pConfig=(SSLConfigRec *)
      ap_get_module_config(r->server->module_config,&apache_ssl_module);
#if APACHE_SSL_EXPORT_CERTS
    SSLDirConfigRec *pDirConfig=(SSLDirConfigRec *)
      ap_get_module_config(r->per_dir_config,&apache_ssl_module);
#endif

    /* Check to see if SSL is on */
    if(pConfig->bDisabled || OPTIONAL_SSL)
	return;

    if(r->connection->client->ssl == NULL)
	return;

    if(r->connection->client->bRevoked)
	ap_table_set(e,pConfig->szOnRevocationSetEnv,"YES");
    if(r->connection->client->bCRLExpired)
	ap_table_set(e,pConfig->szOnCRLExpirySetEnv,"YES");
    if(r->connection->client->bNoCRL)
	ap_table_set(e,pConfig->szOnNoCRLSetEnv,"YES");

    cipher=SSL_get_cipher(r->connection->client->ssl);
    ap_table_set(e,"HTTPS","on");
    ap_table_set(e,"HTTPS_CIPHER",cipher);
    ap_table_set(e,"SSL_CIPHER",cipher);
    ap_table_set(e,"SSL_PROTOCOL_VERSION",
		 SSL_get_version(r->connection->client->ssl));
    ap_table_set(e,"SSL_SSLEAY_VERSION", SSLeay_version(SSLEAY_VERSION));

    for(n=0 ; aCipherspecs[n].szName ; ++n)
	if(!strcmp(cipher,aCipherspecs[n].szName))
	    {
	    keysize=aCipherspecs[n].nKeySize;
	    secretkeysize=aCipherspecs[n].nSecretKeySize;
	    break;
	    }

    t=ap_psprintf(r->pool,"%d",keysize);
    ap_table_setn(e,"HTTPS_KEYSIZE",t);

    t=ap_psprintf(r->pool,"%d",secretkeysize);
    ap_table_setn(e,"HTTPS_SECRETKEYSIZE",t);

    if(r->connection->client->szClientX509)
	{
	ExpandCert(r->pool,e,"SSL_CLIENT_","SSL_CLIENT_DN",
		   r->connection->client->szClientX509);
#if USE_EXTENDED_ENVIRONMENT
	ExpandCert(r->pool,e,"SSL_CLIENT_S_DN_","SSL_CLIENT_S_DN",
		   r->connection->client->szClientX509);
#endif
	xs=SSL_get_peer_certificate(r->connection->client->ssl);
	ExpandCert(r->pool,e,
#if USE_OLD_ENVIRONMENT
		   "SSL_CLIENT_I","SSL_CLIENT_I_DN",
#else
		   "SSL_CLIENT_I_","SSL_CLIENT_I_DN",
#endif
		   X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0));
#if USE_EXTENDED_ENVIRONMENT
	ExpandCert(r->pool,e,"SSL_CLIENT_I_DN_","SSL_CLIENT_I_DN",
		   X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0));
	get_serial(r->pool,e,"SSL_CLIENT_M_SERIAL",xs);
        get_valid(r->pool,e,"SSL_CLIENT_V_START",X509_get_notBefore(xs));
        get_valid(r->pool,e,"SSL_CLIENT_V_END",X509_get_notAfter(xs));
        t=ap_psprintf(r->pool,"%lu",X509_get_version(xs)+1);
        ap_table_setn(e,"SSL_CLIENT_M_VERSION",t);
#endif
#if APACHE_SSL_EXPORT_CERTS
	if(pDirConfig->bExportCerts)
	    AddCertToEnv(r->pool,e,"SSL_CLIENT_CERT",xs);
#endif
	}

    xs=SSL_get_certificate(r->connection->client->ssl);
    ExpandCert(r->pool,e,"SSL_SERVER_","SSL_SERVER_DN",
	       X509_NAME_oneline(X509_get_subject_name(xs),NULL,0));
#if USE_EXTENDED_ENVIRONMENT
    ExpandCert(r->pool,e,"SSL_SERVER_S_","SSL_SERVER_S_DN",
               X509_NAME_oneline(X509_get_subject_name(xs),NULL,0));
#endif
    ExpandCert(r->pool,e,
#if USE_OLD_ENVIRONMENT
	       "SSL_SERVER_I","SSL_SERVER_I_DN",
#else
	       "SSL_SERVER_I_","SSL_SERVER_I_DN",
#endif
	       X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0));
#if USE_EXTENDED_ENVIRONMENT
    ExpandCert(r->pool,e,
	       "SSL_SERVER_I_DN_","SSL_SERVER_I_DN",
	       X509_NAME_oneline(X509_get_issuer_name(xs),NULL,0));
    get_serial(r->pool,e,"SSL_SERVER_M_SERIAL",xs);
    get_valid(r->pool,e,"SSL_SERVER_V_START",X509_get_notBefore(xs));
    get_valid(r->pool,e,"SSL_SERVER_V_END",X509_get_notAfter(xs));
    t=ap_psprintf(r->pool,"%lu",X509_get_version(xs)+1);
    ap_table_setn(e,"SSL_SERVER_M_VERSION",t);
#endif

#if APACHE_SSL_EXPORT_CERTS
# if !APACHE_SSL_KEEP_CERTS
    pCertStack=SSL_get_peer_cert_chain(r->connection->client->ssl);
    for(n=0 ; pCertStack && n < sk_num(pCertStack) ; ++n)
	{
	t=ap_psprintf(r->pool,ENV_CERT_CHAIN "%d",n+1);
	AddCertToEnv(r->connection->client->pool,e,t,(X509 *)sk_value(pCertStack,n));
	}
# else
    if(r->connection->client->tableCertChain && pDirConfig->bExportCerts)
	r->subprocess_env=
	  ap_overlay_tables(r->pool,r->subprocess_env,
			    r->connection->client->tableCertChain);
# endif
#endif

    return;
    }

/*
static int SSLFixups(request_rec *r)
    {
    SSLAddCommonVars(r);
    return DECLINED;
    }
*/

static void *CreateSSLServerConfig(pool *p,server_rec *s)
    {
    SSLConfigRec *rec=ap_pcalloc(p,sizeof(SSLConfigRec));
    
    rec->bDisabled=UNSET;	/* Will become FALSE */
    rec->szCertificateFile=NULL;
    rec->szKeyFile=NULL;
    rec->szCACertificatePath=NULL;
    rec->szCACertificateFile=NULL;
    rec->szReqCiphers=NULL;
    rec->szDNCheckFile=NULL;
#if !RENEG
    rec->nVerifyDepth=0;
    rec->nVerifyClient=VERIFY_UNSET;	/* Will become VERIFY_NONE */
#endif
    rec->px509Certificate=NULL;
    rec->prsaKey=NULL;
    rec->pSSLCtx=NULL;
    rec->bFakeBasicAuth=UNSET;		/* Will become FALSE */
    rec->bNoCAList=UNSET;
    rec->tSessionCacheTimeout=0;
    rec->aRandomFiles=ap_make_array(p,1,sizeof(RandomFile));

    rec->bUseCRL=UNSET;
    rec->bCRLCheckAll=UNSET;
    rec->bNoV2=UNSET;
    rec->szOnRevocationSetEnv=NULL;
    rec->szOnCRLExpirySetEnv=NULL;
    rec->szOnNoCRLSetEnv=NULL;

    return rec;
    }

#define Merge(el,unset)		new->el=(add->el == unset ? base->el : add->el)
#define MergeString(el)		Merge(el,NULL)
#define MergeBool(el)		Merge(el,UNSET)
#define MergeInt(el)		Merge(el,0)

static void *MergeSSLServerConfig(pool *p,void *basev,void *addv)
    {
    SSLConfigRec *base=(SSLConfigRec *)basev;
    SSLConfigRec *add=(SSLConfigRec *)addv;
    SSLConfigRec *new=(SSLConfigRec *)ap_pcalloc(p,sizeof(SSLConfigRec));

    MergeBool(bDisabled);
    MergeString(szCertificateFile);
    MergeString(szKeyFile);
    MergeString(szCACertificatePath);
    MergeString(szCACertificateFile);
    MergeString(szDNCheckFile);
    MergeString(szReqCiphers);
#if !RENEG
    MergeInt(nVerifyDepth);
    Merge(nVerifyClient,VERIFY_UNSET);
#endif
    MergeBool(bFakeBasicAuth);
    MergeBool(bNoCAList);
    MergeInt(tSessionCacheTimeout);
    new->aRandomFiles=ap_append_arrays(p,base->aRandomFiles,add->aRandomFiles);

    MergeBool(bUseCRL);
    MergeBool(bCRLCheckAll);
    MergeBool(bNoV2);
    MergeString(szOnRevocationSetEnv);
    MergeString(szOnCRLExpirySetEnv);
    MergeString(szOnNoCRLSetEnv);

    return new;
    }

static void *CreateSSLDirConfig(pool *p,char *dummy)
    {
    SSLDirConfigRec *rec=ap_pcalloc(p,sizeof(SSLDirConfigRec));

    rec->tbRequiredCiphers=ap_make_table(p,4);
    rec->tbBannedCiphers=ap_make_table(p,4);
    rec->nSSLRequired=SSLREQ_UNSET;
#if APACHE_SSL_EXPORT_CERTS
    rec->bExportCerts=FALSE;
#endif
#if RENEG
    rec->nVerifyDepth=0;
    rec->nVerifyClient=VERIFY_UNSET;	/* Won't become VERIFY_NONE */
#endif

    return rec;
    }

static void *MergeSSLDirConfig(pool *p,void *basev,void *addv)
    {
    SSLDirConfigRec *base=(SSLDirConfigRec *)basev;
    SSLDirConfigRec *add=(SSLDirConfigRec *)addv;
    SSLDirConfigRec *new=(SSLDirConfigRec *)
      ap_palloc(p,sizeof(SSLDirConfigRec));

    new->tbRequiredCiphers=ap_overlay_tables(p,add->tbRequiredCiphers,
					     base->tbRequiredCiphers);
    new->tbBannedCiphers=ap_overlay_tables(p,add->tbBannedCiphers,
					   base->tbBannedCiphers);
    Merge(nSSLRequired,SSLREQ_UNSET);
#if APACHE_SSL_EXPORT_CERTS
    new->bExportCerts=base->bExportCerts || add->bExportCerts;
#endif
#if RENEG
    MergeInt(nVerifyDepth);
    Merge(nVerifyClient,VERIFY_UNSET);
#endif

    return new;
    }

static void InitSSL(server_rec *s)
    {
#if CACHE_SESSIONS
    if(!s_szCacheServerPort)
	{
	ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
		"Required SSLCacheServerPort missing");
	exit(1);
	}
    if(!s_szCacheServerPath)
	{
	ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
		     "Required SSLCacheServerPath missing");
	exit(1);
	}
#endif

    SSL_load_error_strings();
    ERR_load_crypto_strings();
#if SSLEAY_VERSION_NUMBER >= 0x0800
    SSLeay_add_ssl_algorithms();
#else
    /* only enable this with care: it can be used for /tmp symlink attacks.
    SSL_debug("/tmp/ssldebug");*/
#endif
#if ENGINE_SUPPORT
    if(s_szEngineID)
        {
        ENGINE *e = ENGINE_by_id(s_szEngineID);
        if(!e)
            {
            ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID is unrecognised");
            exit(1);
            }
        /* Squeeze in a quick hack - nCipher "chil" units don't do this by
         * default, which means that child processes could inherent the same
         * file-descriptor and corrupt each other's I/O. :-( */
        if(strcmp(s_szEngineID, "chil"))
            ENGINE_ctrl(e, ENGINE_CTRL_CHIL_SET_FORKCHECK, 1, 0, 0);
        if(!ENGINE_set_default(e, ENGINE_METHOD_ALL))
            {
            ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID could not be initialised");
            exit(1);
            }
        ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
                     "SSLEngineID (as supplied) initialised and running");
        if(!ENGINE_get_RSA(e))
            ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID does not support RSA");
        else
            ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID supports RSA");
        if(!ENGINE_get_DSA(e))
            ap_log_error(APLOG_MARK,APLOG_WARNING|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID does not support DSA");
        else
            ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
                         "Supplied SSLEngineID supports DSA");
        ENGINE_free(e);
        }
#endif
    }

#if SSLEAY_VERSION_NUMBER >= 0x0800 && SSLEAY_VERSION_NUMBER < 0x0920
/* FIXME: This ought to generate a new key from time to time - why make the
Feds' life easy? */
static RSA *TmpRSACallback(SSL *pSSL,int nExport)
    {
    static RSA *pRSA=NULL;

    if (pRSA == NULL)
#if SSLEAY_VERSION_NUMBER >= 0x0900
	pRSA=RSA_generate_key(512,RSA_F4,NULL,NULL);
#else
	pRSA=RSA_generate_key(512,RSA_F4,NULL);
#endif
    return pRSA;
    }
#endif

#if SSLEAY_VERSION_NUMBER >= 0x0920
/* FIXME: This ought to generate a new key from time to time - why make the
Feds' life easy? */
static RSA *TmpRSACallback(SSL *pSSL,int nExport,int nKeyLength)
    {
    static RSA *pRSA512=NULL;
    static RSA *pRSA1024=NULL;

    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,
		 SSL_get_ex_data(pSSL,EX_DATA_SERVER_REC),
		 "Generating %d bit key",nKeyLength);
    assert(nExport);  /* Otherwise it was a sign-only key and we should set our own keylength */
    assert(nKeyLength == 512 || nKeyLength == 1024);
    if (pRSA512 == NULL && nKeyLength == 512)
	pRSA512=RSA_generate_key(512,RSA_F4,NULL,NULL);
    if (pRSA1024 == NULL && nKeyLength == 1024)
	pRSA1024=RSA_generate_key(1024,RSA_F4,NULL,NULL);
    
    return nKeyLength == 512 ? pRSA512 : pRSA1024;
    }
#endif

#if CACHE_SESSIONS

#define MAX_SESSION_DER	100000

/*
  If *pbLocal is TRUE, only do a local search. Also, set *pbLocal if the key
  was found locally.
  */
static uchar *CacheFind(uchar *aucKey,int nKey,int *pnData,BOOL *pbLocal)
    {
    Cache *p;
    uchar *t;

    p=LocalCacheFind(aucKey,nKey);

    if(!p)
	{
	uchar *t2;
	time_t tExpiresAt;

	if(*pbLocal)
	    return NULL;

	t=GlobalCacheGet(aucKey,nKey,pnData,&tExpiresAt);
	if(!t)
	    return NULL;
#if DEBUG_SESSIONS
	fprintf(stderr,"Session found in global cache, to=%ld\n", tExpiresAt);
#endif

	t2=malloc(nKey);
	memcpy(t2,aucKey,nKey);
	p=LocalCacheAdd(t2,nKey,tExpiresAt);
	p->aucData=t;
	p->nData=*pnData;
	}
    else
	{
	t=p->aucData;
	*pnData=p->nData;
	*pbLocal=TRUE;
#if DEBUG_SESSIONS
	fprintf(stderr,"Session found in local cache\n");
#endif
	}
    return t;
    }

#if APACHE_SSL_KEEP_CERTS
static uchar *MakeCertKey(Cache *pCache,const char *szKey,int *pnLength)
    {
    uchar *buf=malloc(*pnLength=pCache->nKey+strlen(szKey));
    memcpy(buf,pCache->aucKey,pCache->nKey);
    memcpy(buf+pCache->nKey,szKey,strlen(szKey));

    return buf;
    }

static int SendCertToServer(void *extra,const char *szKey,const char *szValue)
    {
    Cache *pCache=extra;
    int nKey;
    uchar *aucKey;
    Cache *pCertCache;

    aucKey=MakeCertKey(pCache,szKey,&nKey);
    pCertCache=LocalCacheAdd(aucKey,nKey,pCache->tExpiresAt);
    pCertCache->aucData=(uchar *)strdup(szValue);
    pCertCache->nData=strlen(szValue)+1;
    GlobalCacheAdd(pCertCache->aucKey,pCertCache->nKey,pCertCache->aucData,
		   pCertCache->nData,pCertCache->tExpiresAt);
    return 1;
    }

static uchar *CacheFindS(char *aucKey,int nKey,int *pnData,BOOL *pbLocal)
    { return CacheFind((uchar *)aucKey,nKey,pnData,pbLocal); }

static void SendCertChainToServer(Cache *pCache,conn_rec *pCurrentConnection)
    {
    if(!pCurrentConnection->client->tableCertChain)
	return;
    ap_table_do(SendCertToServer,pCache,
		pCurrentConnection->client->tableCertChain,NULL);
    }

static void GetCertChainFromServer(uchar *aucSessionID,int nLength,
				   BOOL bLocal,conn_rec *pCurrentConnection)
    {
    int n;
    char buf[MAX_SESSION_DER];

    ap_assert(nLength < MAX_SESSION_DER);
    memcpy(buf,aucSessionID,nLength);
    for(n=1 ; ; ++n)
	{
	uchar *t;
	int nData;
	int l;

	ap_snprintf(buf+nLength,sizeof buf-nLength,ENV_CERT_CHAIN "%d",n);
	l=nLength+strlen(buf+nLength);

	t=CacheFindS(buf,l,&nData,&bLocal);
	if(!t)
	    break;

	if(!pCurrentConnection->client->tableCertChain)
	    pCurrentConnection->client->tableCertChain=
	      ap_make_table(pCurrentConnection->client->pool,5);

	ap_table_set(pCurrentConnection->client->tableCertChain,
		     buf+nLength,(char *)t);
	}
    }
#endif /* APACHE_SSL_KEEP_CERTS */

static void SendSessionToServer(SSL_SESSION *pSession,
				conn_rec *pCurrentConnection)
    {
    Cache *p;
    uchar *t;
    int nLength;
    SSLConfigRec *pConfig;
    time_t tExpiresAt;

    if(LocalCacheFind(pSession->session_id,pSession->session_id_length))
	return;

    pConfig=ap_get_module_config(pCurrentConnection->server->module_config,
				 &apache_ssl_module);
    tExpiresAt=time(NULL)+pConfig->tSessionCacheTimeout;
    SSL_set_timeout(pSession, pConfig->tSessionCacheTimeout);

    t=malloc(pSession->session_id_length);
    memcpy(t,pSession->session_id,pSession->session_id_length);
    p=LocalCacheAdd(t,pSession->session_id_length,tExpiresAt);

    nLength=i2d_SSL_SESSION(pSession,NULL);
    if(nLength <= 0)
	SSLLogError(pCurrentConnection->server,APLOG_ERR,"Session pickle failed");
    assert(nLength > 0);
    assert(nLength <= MAX_SESSION_DER);

    p->aucData=malloc(nLength);
    t=p->aucData;
    nLength=i2d_SSL_SESSION(pSession,&t);
    p->nData=nLength;

    GlobalCacheAdd(p->aucKey,p->nKey,p->aucData,p->nData,tExpiresAt);
#if APACHE_SSL_KEEP_CERTS
    SendCertChainToServer(p,pCurrentConnection);
#endif
    }

static SSL_SESSION *GetSessionFromServer(uchar *aucSessionID,int nLength,
					 conn_rec *pCurrentConnection)
    {
    SSL_SESSION *pSession;
    const uchar *t;
    int nData;
    BOOL bLocal=FALSE;

#if APACHE_SSL_KEEP_CERTS
    GetCertChainFromServer(aucSessionID,nLength,bLocal,pCurrentConnection);
#endif

    t=CacheFind(aucSessionID,nLength,&nData,&bLocal);
    if(!t)
	{
#if DEBUG_SESSIONS
	fprintf(stderr,"Session not found\n");
#endif
	return NULL;
	}

    pSession=d2i_SSL_SESSION(NULL,&t,nData);
    if(!pSession)
	SSLLogErrorToFile(stderr);
    assert(pSession);

    return pSession;
    }

static int NewSessionCacheCallback(SSL *pSSL,SSL_SESSION *pNew)
    {
    conn_rec *pCurrentConnection=SSL_get_ex_data(pSSL,EX_DATA_CONN_REC);
#if DEBUG_SESSIONS
    int n;

    fprintf(stderr,"NewSessionCacheCallback(");
    for(n=0 ; n < pNew->session_id_length ; ++n)
	fprintf(stderr,"%02x",pNew->session_id[n]);
    fprintf(stderr,")\n");
#endif

    if(pCurrentConnection->client->bRevoked
       || pCurrentConnection->client->bCRLExpired
       || pCurrentConnection->client->bNoCRL)
	{
	SSL_CTX_remove_session(pSSL->ctx,pNew);
	return 0;
	}
    SendSessionToServer(pNew,pCurrentConnection);
    return 0;
    }

static SSL_SESSION *GetSessionCacheCallback(SSL *pSSL,
					    unsigned char *aucSessionID,
					    int nLength,int *pCopy)
    {
    SSL_SESSION *pSession;

#if DEBUG_SESSIONS
    int n;

    fprintf(stderr,"GetSessionCacheCallback(");
    for(n=0 ; n < nLength ; ++n)
	fprintf(stderr,"%02x",aucSessionID[n]);
    fprintf(stderr,")\n");
#endif
    *pCopy=0;
    pSession=GetSessionFromServer(aucSessionID,nLength,
				  SSL_get_ex_data(pSSL,EX_DATA_CONN_REC));

    return pSession;
    }

#endif /* CACHE_SESSIONS */

static void GetPrivateKey(server_rec *s,SSLConfigRec *pConfig,
			  const char *szKeyFile)
    {
    static table *s_pKeyTable;
    RSA *prsaKey;
    FILE *f;
    int nCount;

    if(!s_pKeyTable)
	{
	s_pKeyTable=ap_make_table(s_pPermanentPool,10);
	assert(s_pKeyTable);
	}
    prsaKey=(RSA *)ap_table_get(s_pKeyTable,szKeyFile);
    if(prsaKey)
	{
	pConfig->prsaKey=prsaKey;
	return;
	}

    fprintf(stderr,"Reading key for server %s:%d\n",s->server_hostname,
	    s->port);

    f=fopen(szKeyFile,"r");
    if(!f)
	{
	ap_log_error(APLOG_MARK,APLOG_CRIT,s,"Can't open key file %s",
		     szKeyFile);
	exit(1);
	}

    pConfig->prsaKey=RSA_new();
    for(nCount=0 ; !localPEM_read_RSAPrivateKey(f,&pConfig->prsaKey,NULL)
	  ; ++nCount)
	{
	if(nCount < 3 && ERR_peek_error() == ERR_PACK(ERR_LIB_EVP,
						      DECRYPTFINAL,
						      EVP_R_BAD_DECRYPT))
	    {
	    fprintf(stderr,"Bad passphrase - try again\n");
	    rewind(f);
	    continue;
	    }
	ap_log_error(APLOG_MARK,APLOG_CRIT,s,
		     "Error reading private key file %s: ",szKeyFile);
	SSLLogError(s,APLOG_CRIT,NULL);
	exit(1);
	}
    fclose(f);

    ap_table_setn(s_pKeyTable,ap_pstrdup(s_pPermanentPool,szKeyFile),
		  (const char *)pConfig->prsaKey);
    }

#if SSLEAY_VERSION_NUMBER < 0x0920
static int X509NameCompare(X509_NAME **a,X509_NAME **b)
    {
    return X509_NAME_cmp(*a,*b);
    }
#endif

static void InitCRL(SSLConfigRec *pConfig)
    {
    X509_STORE *pStore=SSL_CTX_get_cert_store(pConfig->pSSLCtx);
    int vflags=0;

    if(pConfig->bUseCRL)
	vflags|=X509_V_FLAG_CRL_CHECK;
    if(pConfig->bCRLCheckAll)
	vflags|=X509_V_FLAG_CRL_CHECK_ALL;
    X509_STORE_set_flags(pStore,vflags);
    }

static void GetCertificateAndKey(server_rec *s,pool *p,SSLConfigRec *pConfig)
    {
    FILE *f;
    char szPath[MAX_STRING_LEN];
#if SSLEAY_VERSION_NUMBER >= 0x0920
    STACK_OF(X509_NAME) *stackCAs=NULL;
#endif

    if(pConfig->px509Certificate)
	{
	ap_log_error(APLOG_MARK,APLOG_NOERRNO|APLOG_CRIT,s,
		     "Attempt to reinitialise SSL for server %s",
		     s->server_hostname);
	exit(1);
	}

#if SSLEAY_VERSION_NUMBER < 0x0800
    pConfig->pSSLCtx=SSL_CTX_new();
#else
    pConfig->pSSLCtx=SSL_CTX_new(SSLv23_server_method());
    if(pConfig->bNoV2)
	SSL_CTX_set_options(pConfig->pSSLCtx,SSL_OP_NO_SSLv2);
# if RENEG
//    s_nVerifyClient=VERIFY_OPTIONAL_NO_CA;
    SSL_CTX_set_verify(pConfig->pSSLCtx,0,ApacheSSLVerifyCallback);
# else
    SSL_CTX_set_verify(pConfig->pSSLCtx,VerifyFlags(pConfig),ApacheSSLVerifyCallback);
# endif
#endif

    InitCRL(pConfig);

#if CACHE_SESSIONS
    SSL_CTX_sess_set_new_cb(pConfig->pSSLCtx,NewSessionCacheCallback);
    SSL_CTX_sess_set_get_cb(pConfig->pSSLCtx,GetSessionCacheCallback);
# if APACHE_SSL_EXPORT_CERTS
    SSL_CTX_set_session_cache_mode(pConfig->pSSLCtx,
				   SSL_CTX_get_session_cache_mode(pConfig
								  ->pSSLCtx)
				   |SSL_SESS_CACHE_NO_INTERNAL_LOOKUP);
# endif
#endif

    if(pConfig->szReqCiphers != NULL)
	{
	if(!SSL_CTX_set_cipher_list(pConfig->pSSLCtx,pConfig->szReqCiphers))
	    {
	    SSLLogError(s,APLOG_CRIT,"unable to set ciphers");
	    exit(1);
	    }
	}

    if(((pConfig->szCACertificateFile || pConfig->szCACertificatePath)
	&& !SSL_CTX_load_verify_locations(pConfig->pSSLCtx,
					   pConfig->szCACertificateFile,
					   pConfig->szCACertificatePath)
	)
       || !SSL_CTX_set_default_verify_paths(pConfig->pSSLCtx))
	{
	SSLLogError(s,APLOG_CRIT,"error setting verify locations");
	exit(1);
	}

    f=fopen(pConfig->szCertificateFile,"r");
    if(!f)
	{
	ap_snprintf(szPath,sizeof szPath,"%s/%s",X509_get_default_cert_dir(),
		pConfig->szCertificateFile);
	f=fopen(szPath,"r");
	if(!f)
	    {
	    ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
			 "Can't open certificate file %s, nor %s",
			 pConfig->szCertificateFile,szPath);
	    exit(1);
	    }
	}
    else
	strcpy(szPath,pConfig->szCertificateFile);  /* in case it also contains the key */

    pConfig->px509Certificate=X509_new();

    if(!localPEM_read_X509(f,&pConfig->px509Certificate,NULL))
	{
	ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
		     "Error reading server certificate file %s",szPath);
	SSLLogError(s,APLOG_CRIT,NULL);
	exit(1);
	}
    fclose(f);

    if(pConfig->szKeyFile)
	if(*pConfig->szKeyFile == '/')
	    strcpy(szPath,pConfig->szKeyFile);
	else
	    ap_snprintf(szPath,sizeof szPath,"%s/private/%s",
			X509_get_default_cert_area(),pConfig->szKeyFile);

/* Otherwise the path already contains the name of the certificate file */
    GetPrivateKey(s,pConfig,szPath);

#if SSLEAY_VERSION_NUMBER >= 0x0800
    SSL_CTX_set_tmp_rsa_callback(pConfig->pSSLCtx,TmpRSACallback);
#endif

#if SSLEAY_VERSION_NUMBER >= 0x0920
#if RENEG
    if(!pConfig->bNoCAList)
#else
    if(pConfig->nVerifyClient != VERIFY_OPTIONAL_NO_CA && !pConfig->bNoCAList)
#endif
	{
	if(pConfig->szCACertificateFile || pConfig->szCACertificatePath)
	    stackCAs=sk_X509_NAME_new(NULL);

	if(pConfig->szCACertificateFile
	   && !SSL_add_file_cert_subjects_to_stack(stackCAs,
					      pConfig->szCACertificateFile))
	    {
	    SSLLogError(s,APLOG_CRIT,"error reading CA certs");
	    exit(1);
	    }

	if(pConfig->szCACertificatePath
	   && !SSL_add_dir_cert_subjects_to_stack(stackCAs,
					     pConfig->szCACertificatePath))
	    {
	    SSLLogError(s,APLOG_CRIT,"error reading CA certs");
	    exit(1);
	    }

	if(stackCAs && sk_X509_NAME_num(stackCAs) > 0)
	    SSL_CTX_set_client_CA_list(pConfig->pSSLCtx,stackCAs);
	}
#elif SSLEAY_VERSION_NUMBER >= 0x0800
    if(pConfig->szCACertificateFile)
	SSL_CTX_set_client_CA_list(pConfig->pSSLCtx,
		SSL_load_client_CA_file(pConfig->szCACertificateFile));

    if(pConfig->szCACertificatePath)
	{
	pool *p2=ap_make_sub_pool(p);
	DIR *d=ap_popendir(p2,pConfig->szCACertificatePath);
	struct DIR_TYPE *dstruct;
	STACK *stackCAs;

	/* Note that a side effect is that the CAs will be sorted by name */
	stackCAs=sk_new(X509NameCompare);
	if(!d)
	    {
	    ap_log_error(APLOG_MARK,APLOG_ERR,s,"Can't open directory %s",
			 pConfig->szCACertificatePath);
	    exit(1);
	    }

	while((dstruct=readdir(d)))
	    {
	    STACK *s;
	    int n;
	    char *szFile;
	    
	    szFile=ap_pstrcat(p2,pConfig->szCACertificatePath,"/",
			      dstruct->d_name,NULL);
	    s=SSL_load_client_CA_file(szFile);
	    for(n=0 ; s && n < sk_num(s) ; ++n)
		{
		if(sk_find(stackCAs,sk_value(s,n)) >= 0)
		    continue;
	        sk_push(stackCAs,sk_value(s,n));
		}
	    }

	sk_set_cmp_func(stackCAs,NULL);
	SSL_CTX_set_client_CA_list(pConfig->pSSLCtx,stackCAs);
	ap_pclosedir(p2,d);
	ap_destroy_pool(p2);
	}
#endif
    }

#if CACHE_SESSIONS
static server_rec *s_pServer;
extern uid_t ap_user_id;

static int CacheLauncher(void *cmd,child_info *pInfo)
    {
    char a1[1024];

    ap_error_log2stderr(s_pServer);
    dup2(STDERR_FILENO,STDOUT_FILENO);
    ap_snprintf(a1,sizeof a1,"%d",ap_user_id);
    ap_cleanup_for_exec();
    if(s_szCacheServerDir)
	{
	if(chdir(s_szCacheServerDir) < 0)
	    perror(s_szCacheServerDir);
	}
    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s_pServer,
		 "gcache pid is %d",getpid());
    execl(cmd,cmd,a1,s_szCacheServerPort,s_szSplashUnicast,s_szCacheServerArg1,
	  s_szCacheServerArg2,NULL);
    perror(cmd);
    assert(0);
    return 1;
    }

static void LaunchCache(server_rec *s,pool *pPool)
    {
    int pid;

    s_pServer=s;
    printf("Launching... %s\n",s_szCacheServerPath);
    pid=ap_spawn_child(pPool,CacheLauncher,s_szCacheServerPath,kill_always,
		       NULL,NULL,NULL);
    printf("pid=%d\n",pid);
    }
#endif

static void InitSSLServer(server_rec *s,pool *p)
    {
    int n;
    server_rec *pMainServer=s;
    int nSaveStderr;

    /* If you were going to make Apache-SSL add a version string by the new
       method, here's where to do it. However, it doesn't seem right to me to
       do that, since if APACHE_SSL is defined, the server is modified,
       regardless of whether this module is included. - Ben 3/8/98

       ap_add_version_component(APACHE_SSL_VERSION);

       */

    if(!s_pPermanentPool)
	{
	s_pPermanentPool=ap_make_sub_pool(NULL);
	assert(s_pPermanentPool);
	}

    nSaveStderr=dup(2);
    close(2);
    n=dup(1);
    assert(n == 2);

    InitSSL(s);
    for ( ; s ; s=s->next)
	{
	SSLConfigRec *pConfig=ap_get_module_config(s->module_config,
						   &apache_ssl_module);

	/* Fix up stuff that may not have been set */

	if(pConfig->bDisabled == UNSET)
	    pConfig->bDisabled=FALSE;
#if !RENEG
	if(pConfig->nVerifyClient == VERIFY_UNSET)
	    pConfig->nVerifyClient=VERIFY_NONE;
#endif
	if(pConfig->bFakeBasicAuth == UNSET)
	    pConfig->bFakeBasicAuth=FALSE;
	if(pConfig->bNoCAList == UNSET)
	    pConfig->bNoCAList=FALSE;
	if(pConfig->bUseCRL == UNSET)
	    pConfig->bUseCRL=FALSE;
	if(pConfig->bCRLCheckAll == UNSET)
	    pConfig->bCRLCheckAll=FALSE;
	if(pConfig->bNoV2 == UNSET)
	    pConfig->bNoV2=FALSE;
	/* End of fixups */

	if(pConfig->bDisabled)
	    {
	    if(!s->port)
		s->port=DEFAULT_HTTP_PORT;
	    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,s,
			 "SSL disabled for server %s:%d\n",
			 s->server_hostname,s->port);
	    continue;
	    }
#if CACHE_SESSIONS
	if(!pConfig->tSessionCacheTimeout)
	    {
	    ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
			 "SSLSessionCacheTimeout not set");
	    exit(1);
	    }
#endif
	if(!s->port)
	    s->port=DEFAULT_HTTPS_PORT;

	if(!pConfig->szCertificateFile)
	    {
	    ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
			 "No SSL Certificate set for server %s:%d",
			 s->server_hostname,s->port);
	    exit(1);
	    }
#if !RENEG
	if(pConfig->nVerifyClient < 0
	   || pConfig->nVerifyClient > VERIFY_OPTIONAL_NO_CA)
	    {
	    ap_log_error(APLOG_MARK,APLOG_CRIT|APLOG_NOERRNO,s,
			 "Bad value for SSLVerifyClient (%d)",
			 pConfig->nVerifyClient);
	    exit(1);
	    }
#endif
	GetCertificateAndKey(s,p,pConfig);
	}
#if CACHE_SESSIONS
    LaunchCache(pMainServer,p);
#endif
    close(2);
    n=dup(nSaveStderr);
    assert(n == 2);
    close(nSaveStderr);
    }

/*
Fake a Basic authentication from the X509 client certificate.

This must be run fairly early on to prevent a real authentication from
occuring, in particular it must be run before anything else that authenticates
a user.

This means that the Module statement for this module should be LAST in the
Configuration file.
*/

static int FakeBasicAuth(request_rec *r)
    {
    SSLConfigRec *pConfig=ap_get_module_config(r->server->module_config,
					       &apache_ssl_module);
    char b1[MAX_STRING_LEN],b2[MAX_STRING_LEN];
    char *t;
  
    if(OPTIONAL_SSL)
	return DECLINED;
    if(!pConfig->bFakeBasicAuth)
	return DECLINED;
    if(r->connection->user)
	return DECLINED;

    /*
      Fake a password - which one would be immaterial, as, it seems, an empty
      password in the users file would match ALL incoming passwords, if only we
      were using the standard crypt library routine. Unfortunately, SSLeay
      "fixes" a "bug" in crypt and thus prevents blank passwords from working.
      (IMHO what they really fix is a bug in the users of the code - failing to
      program correctly for shadow passwords).
      We need, therefore, to provide a password. This password can be matched
      by adding the string "xxj31ZMTZzkVA" as the password in the user file.
      */
    ap_snprintf(b1,sizeof b1,"%s:password",
		r->connection->client->szClientX509
		? r->connection->client->szClientX509 : "");
    uuencode(b2,b1,FALSE);
    t=ap_psprintf(r->pool,"Basic %s",b2);
    ap_table_setn(r->headers_in,"Authorization",t);
    return DECLINED;
    }

static BOOL ApacheSSLSetCertStuff(conn_rec *conn)
    {
    SSLConfigRec *pConfig=ap_get_module_config(conn->server->module_config,
					       &apache_ssl_module);
    SSL *con=conn->client->ssl;
    char *cert_file=pConfig->szCertificateFile;

/*  PEM_set_getkey_callback(ApacheSSLKeyCallback); */
    if(cert_file != NULL)
	{
	if(SSL_use_certificate(con,pConfig->px509Certificate) <= 0)
	    {
	    SSLLogError(conn->server,APLOG_CRIT,"unable to set certificate");
	    return FALSE;
	    }
	if(SSL_use_RSAPrivateKey(con,pConfig->prsaKey) <= 0)
	    {
	    SSLLogError(conn->server,APLOG_CRIT,"unable to set private key");
	    return FALSE;
	    }
	}
    return TRUE;
    }

static void ApacheSSLSetupVerify(conn_rec *conn)
    {
#if SSLEAY_VERSION_NUMBER < 0x0800
    SSLConfigRec *pConfig=ap_get_module_config(conn->server->module_config,
					       &apache_ssl_module);
    int nVerify=VerifyFlags(pConfig);
#endif

    conn->client->szClientX509=NULL;

    /* FIXME: this is not ever set, it seems */
    conn->client->nVerifyError=X509_V_OK;

    conn->client->bRevoked=FALSE;
    conn->client->bCRLExpired=FALSE;
    conn->client->bNoCRL=FALSE;

#if SSLEAY_VERSION_NUMBER < 0x0800
/* Why call this twice?? Ben */  
    SSL_set_verify(conn->client->ssl,nVerify,ApacheSSLVerifyCallback);
#endif
    if(!ApacheSSLSetCertStuff(conn))
	{
	ap_log_error(APLOG_MARK,APLOG_ERR|APLOG_NOERRNO,conn->server,
		     "ApacheSSLSetCertStuff failed");
	exit(1);
	}
#if SSLEAY_VERSION_NUMBER < 0x0800
    SSL_set_verify(conn->client->ssl,nVerify,ApacheSSLVerifyCallback);
#endif
    }

#if FREE_SESSION
static void CloseSSL(void *_conn)
    {
    conn_rec *conn=_conn;
    SSL *ssl=conn->client->ssl;

    SSL_free(ssl);
    /*    ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,conn->server,
		 "Closed");*/
    }
#endif

static int SSLSetupConnection(conn_rec *conn)
    {
    server_rec *srvr=conn->server;
    BUFF *fb=conn->client;
    SSLConfigRec *pConfig=ap_get_module_config(srvr->module_config,
					       &apache_ssl_module);
    int r;

    if(pConfig->bDisabled)
	{
	fb->ssl=NULL;
	return TRUE;
	}

    fb->bRevoked=fb->bCRLExpired=fb->bNoCRL=FALSE;

    ap_conn_timeout("SSL accept timeout",conn);

    ReadRandomFiles(srvr,(RandomFile *)pConfig->aRandomFiles->elts,
		    pConfig->aRandomFiles->nelts);

#if APACHE_SSL_KEEP_CERTS
    fb->tableCertChain=NULL;
#endif
#if SSLEAY_VERSION_NUMBER >= 0x0920
    /* Use our current configuration as the session ID context. Do this by
       actually using its address, which should be invariant */
    fb->ssl=SSL_new(pConfig->pSSLCtx);
    r=SSL_set_session_id_context(fb->ssl,(unsigned char *)&pConfig,
				 sizeof pConfig);
    ap_assert(r);
#else
    fb->ssl=SSL_new(pConfig->pSSLCtx);
#endif
    SSL_set_fd(fb->ssl,fb->fd);
#if FREE_SESSION
    ap_register_cleanup(conn->pool,conn,CloseSSL,CloseSSL);
#endif
#if DEBUG_SSL_STATE
    SSL_set_info_callback(fb->ssl,InfoCallback);
    SSL_set_ex_data(fb->ssl,EX_DATA_SERVER_REC,srvr);
#endif
    SSL_set_ex_data(fb->ssl,EX_DATA_CONN_REC,conn);

    ApacheSSLSetupVerify(conn);

    while(!SSL_is_init_finished(fb->ssl))
	{
	int ret=SSL_accept(fb->ssl);
	if(ret == 0)
	    {
	    /* This appears to happen when a connection is closed without any
	       data being sent. Since Netscape does this all the time, don't
	       log as an error.
	       */
	    SSLLogError(srvr,APLOG_DEBUG,"SSL_accept returned 0");
	    fb->flags |= B_EOF | B_EOUT;
	    return FALSE;
	    }
	if (ret < 0)
	    {
	    SSLLogError(srvr,APLOG_ERR,"SSL_accept failed");
	    fb->flags |= B_EOF | B_EOUT;
	    return FALSE;
	    }

	if(conn->client->nVerifyError != X509_V_OK)
	    {
	    SSLLogError(srvr,APLOG_ERR,"Verification failed");
	    fb->flags |= B_EOF | B_EOUT;
	    return FALSE;
	    }

#if !RENEG
	if(pConfig->nVerifyClient != VERIFY_NONE)
	    {
	    char *s;
	    X509 *xs;
	    xs=SSL_get_peer_certificate(fb->ssl);
	    if(xs)
		{
		s=X509_NAME_oneline(X509_get_subject_name(xs),NULL,0);
		conn->client->szClientX509=ap_pstrdup(conn->pool,s);
		free(s);
		}
	    }

	if(pConfig->nVerifyClient == VERIFY_REQUIRE
	   && !conn->client->szClientX509)
	    {
	    SSLLogError(conn->server,APLOG_ERR,"No client certificate");
	    return 0;
	    }
#endif
	ap_log_error(APLOG_MARK,APLOG_DEBUG|APLOG_NOERRNO,srvr,"CIPHER is %s",
		     SSL_get_cipher(conn->client->ssl));
	}

#if !RENEG
    /* This should be safe.... so I'll use it */
    SSL_set_read_ahead(fb->ssl,1);
#endif

    return TRUE;
    }

static void SSLSetupConnection2(conn_rec *conn)
    { SSLSetupConnection(conn); }

static const char *SSLLogFile(cmd_parms *cmd,char *struct_ptr,char *arg)
    {
    return "The SSLLogFile directive is no longer used. All logging information now goes to the error log.";
    }

#if RENEG
static const char *SSLVerifyClient(cmd_parms *cmd,SSLDirConfigRec *rec,
				   char *level)
    {
    rec->nVerifyClient=atoi(level);
    if(rec->nVerifyClient < 0 || rec->nVerifyClient > 3)
	return "Bad value for SSLVerifyClient";
    return NULL;
    }
#endif

static const char *SSLRequireCipher(cmd_parms *cmd,SSLDirConfigRec *rec,
				    char *cipher)
    {
    ap_table_set(rec->tbRequiredCiphers,cipher,"Required");
    return NULL;
    }

static const char *SSLBanCipher(cmd_parms *cmd,SSLDirConfigRec *rec,
				char *cipher)
    {
    ap_table_set(rec->tbBannedCiphers,cipher,"Banned");
    return NULL;
    }

static const char *SSLRequireSSL(cmd_parms *cmd,SSLDirConfigRec *rec,
				 char *cipher)
    {
    rec->nSSLRequired=SSLREQ_REQUIRED;
    return NULL;
    }

static const char *SSLDenySSL(cmd_parms *cmd,SSLDirConfigRec *rec,
				 char *cipher)
    {
    rec->nSSLRequired=SSLREQ_DENIED;
    return NULL;
    }

#if APACHE_SSL_EXPORT_CERTS
static const char *SSLExportCerts(cmd_parms *cmd,SSLDirConfigRec *rec,
				  char *cipher)
    {
    rec->bExportCerts=TRUE;
    return NULL;
    }
#endif

#if CACHE_SESSIONS
static const char *SSLCacheServerPort(cmd_parms *cmd,char *struct_ptr,
				      char *arg)
    {
    if(strspn(arg,"0123456789") == strlen(arg))
	s_szCacheServerPort=arg;
    else
	s_szCacheServerPort=ap_server_root_relative(cmd->pool,arg);
    InitGlobalCache(s_szCacheServerPort);

    return NULL;
    }

static const char *SSLCacheServerPath(cmd_parms *cmd,char *struct_ptr,
				      char *arg,char *arg2,char *arg3)
    {
    s_szCacheServerPath=ap_server_root_relative(cmd->pool,arg);
    s_szCacheServerArg1=arg2;
    s_szCacheServerArg2=arg3;

    return NULL;
    }

static const char *SSLCacheServerDir(cmd_parms *cmd,char *struct_ptr,
				     char *arg)
    {
    s_szCacheServerDir=ap_server_root_relative(cmd->pool,arg);

    return NULL;
    }

static const char *SSLSplashUnicast(cmd_parms *cmd,char *struct_ptr,
				    char *arg)
    {
    s_szSplashUnicast=arg;

    return NULL;
    }
#endif

#if ENGINE_SUPPORT
static const char *SSLEngineID(cmd_parms *cmd,char *struct_ptr,char *arg)
    {
    s_szEngineID=arg;
    return NULL;
    }
#endif

static void SetupRandomFile(RandomFile *prf,char *arg1,char *arg2,char *arg3)
    {
    if(!arg3)
	{
	arg3=arg2;
	arg2=arg1;
	arg1="file";
	}

    prf->eType=strcasecmp(arg1,"file") ? RAND_EGD : RAND_FILE;
    prf->szFile=arg2;
    prf->nBytes=atoi(arg3);
    }

static const char *SSLRandomFile(cmd_parms *cmd,char *struct_ptr,char *arg1,
				 char *arg2,char *arg3)
    {
    RandomFile rf;

    SetupRandomFile(&rf,arg1,arg2,arg3);
    ReadRandomFiles(cmd->server,&rf,1);

    return NULL;
    }

static const char *SSLRandomFilePerConnection(cmd_parms *cmd,char *struct_ptr,
					      char *arg1,char *arg2,char *arg3)
    {
    SSLConfigRec *pConfig=ap_get_module_config(cmd->server->module_config,
					       &apache_ssl_module);
    RandomFile *prf=ap_push_array(pConfig->aRandomFiles);

    SetupRandomFile(prf,arg1,arg2,arg3);

    return NULL;
    }

static const char *set_server_string_slot (cmd_parms *cmd,char *struct_ptr,
					   char *arg)
    {
    char *pConfig=ap_get_module_config(cmd->server->module_config,
				       &apache_ssl_module);
  
    int offset=(int)(long)cmd->info; 
    *(char **)(pConfig+offset)=arg;
    return NULL;
    }

#if RENEG
static const char *set_dir_int_slot(cmd_parms *cmd,char *pStruct,char *szValue)
    {
    int offset=(int)(long)cmd->info;
    *(int *)(pStruct+offset)=atoi(szValue);
    return NULL;
    }
#endif

static const char *set_server_int_slot (cmd_parms *cmd,char *struct_ptr,
					char *arg)
    {
    char *pConfig=ap_get_module_config(cmd->server->module_config,
				       &apache_ssl_module);
  
    int offset=(int)(long)cmd->info; 
    *(int *)(pConfig+offset)=atoi(arg);
    return NULL;
    }

static const char *set_server_bool_slot (cmd_parms *cmd,char *struct_ptr)
    {
    char *pConfig=ap_get_module_config(cmd->server->module_config,
				       &apache_ssl_module);
  
    int offset=(int)(long)cmd->info; 
    *(BOOL *)(pConfig+offset)=TRUE;
    return NULL;
    }

static const char *set_server_bool_slot_false (cmd_parms *cmd,char *struct_ptr)
    {
    char *pConfig=ap_get_module_config(cmd->server->module_config,
				       &apache_ssl_module);
  
    int offset=(int)(long)cmd->info; 
    *(BOOL *)(pConfig+offset)=FALSE;
    return NULL;
    }

command_rec ssl_cmds[]=
    {
    { "SSLDisable",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bDisabled),RSRC_CONF,NO_ARGS,
      "Disable SSL" },
    { "SSLEnable",set_server_bool_slot_false,
      (void *)XtOffsetOf(SSLConfigRec,bDisabled),RSRC_CONF,NO_ARGS,
      "Enable SSL" },
    { "SSLCertificateFile",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szCertificateFile),RSRC_CONF,TAKE1,
      "PEM certificate file" },
    { "SSLCertificateKeyFile",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szKeyFile),RSRC_CONF,TAKE1,
      "Certificate private key file (assumed to be SSLCertificateFile if absent)" },
    { "SSLCACertificatePath",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szCACertificatePath),RSRC_CONF,TAKE1,
      "CA Certificate path (taken from SSL_CERT_DIR if absent)" },
    { "SSLCACertificateFile",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szCACertificateFile),RSRC_CONF,TAKE1,
      "CA Certificate file (taken from SSL_CERT_FILE if absent)" },
    { "SSLNoCAList",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bNoCAList),RSRC_CONF,NO_ARGS,
      "Don't send the CA list to the client" },
#if RENEG
    { "SSLVerifyDepth",set_dir_int_slot,
      (void *)XtOffsetOf(SSLDirConfigRec,nVerifyDepth),OR_FILEINFO,TAKE1,
      "Verify depth (default 0)" },
    { "SSLVerifyClient",SSLVerifyClient,NULL,OR_FILEINFO,TAKE1,
      "Verify client (0=no,1=optional,2=required,3=required (CA optional)" },
#else
    { "SSLVerifyDepth",set_server_int_slot,
      (void *)XtOffsetOf(SSLConfigRec,nVerifyDepth),RSRC_CONF,TAKE1,
      "Verify depth (default 0)" },
    { "SSLVerifyClient",set_server_int_slot,
      (void *)XtOffsetOf(SSLConfigRec,nVerifyClient),RSRC_CONF,TAKE1,
      "Verify client (0=no,1=optional,2=required" },
#endif
    { "SSLFakeBasicAuth",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bFakeBasicAuth),RSRC_CONF,NO_ARGS,
      "Translate client X509 into a user name" },
    { "SSLCheckClientDN",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szDNCheckFile),RSRC_CONF,TAKE1,
      "Check client DN against a list in a file" },
    { "SSLRequiredCiphers",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szReqCiphers),RSRC_CONF,TAKE1,
      "Colon-delimited list of required ciphers" },
    /* Per Directory */
    { "SSLRequireCipher",SSLRequireCipher,NULL,OR_FILEINFO,ITERATE,
      "Add a cipher to the per directory list of required ciphers" },
    { "SSLBanCipher",SSLBanCipher,NULL,OR_FILEINFO,ITERATE,
      "Add a cipher to the per directory list of banned ciphers" },
    { "SSLRequireSSL",SSLRequireSSL,NULL,OR_FILEINFO,NO_ARGS,
      "Require SSL (use to protect directories that should not be served unencrypted)" },
    { "SSLDenySSL",SSLDenySSL,NULL,OR_FILEINFO,NO_ARGS,
      "Deny SSL (use to constrain directories that should be served unencrypted)" },
#if APACHE_SSL_EXPORT_CERTS
    { "SSLExportClientCertificates",SSLExportCerts,NULL,OR_FILEINFO,NO_ARGS,
      "Export client certificates and their certificate chain" },
#endif
#if CACHE_SESSIONS
    { "SSLCacheServerPort",SSLCacheServerPort,NULL,RSRC_CONF,TAKE1,
      "Cache server TCP port number" },
    { "SSLSessionCacheTimeout",set_server_int_slot,
      (void *)XtOffsetOf(SSLConfigRec,tSessionCacheTimeout),RSRC_CONF,TAKE1,
      "Lifetime in seconds of session cache objects" },
    { "SSLCacheServerPath",SSLCacheServerPath,NULL,RSRC_CONF,TAKE123,
      "Path of the global cache server and up to two arguments" },
    { "SSLCacheServerRunDir",SSLCacheServerDir,NULL,RSRC_CONF,TAKE1,
      "Path for the server to run in (in case of coredumps)" },
    { "SSLSplashUnicast",SSLSplashUnicast,NULL,RSRC_CONF,TAKE1,
      "Unicast IP address for Splash! (only needed with splashcache)" },
#endif
#if ENGINE_SUPPORT
    { "SSLEngineID",SSLEngineID,NULL,RSRC_CONF,TAKE1,
      "Alternative OpenSSL `ENGINE id' to use" },
#endif
    { "SSLRandomFile",SSLRandomFile,NULL,RSRC_CONF,TAKE23,
      "A type (file or EGD), a file to read randomness from and a maximum number of bytes to read" },
    { "SSLRandomFilePerConnection", SSLRandomFilePerConnection,NULL,RSRC_CONF,
      TAKE23,
      "A type (file or EGD), a file to read randomness from and a maximim number of bytes to read on each request" },
    { "SSLLogFile",SSLLogFile,NULL,RSRC_CONF,TAKE1,
      "Defunct SSL log file - DO NOT USE" },
    { "SSLUseCRL",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bUseCRL),RSRC_CONF,NO_ARGS,
      "Use a CRL file (found in SSLCACertificatePath)" },
    { "SSLCRLCheckAll",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bCRLCheckAll),RSRC_CONF,NO_ARGS,
      "When using a CRL (see SSLUseCRL), check the entire chain" },
    { "SSLOnRevocationSetEnv",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szOnRevocationSetEnv),RSRC_CONF,TAKE1,
      "Set an environment variable on certificate revocation" },
    { "SSLOnCRLExpirySetEnv",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szOnCRLExpirySetEnv),RSRC_CONF,TAKE1,
      "Set an environment variable on CRL expiry" },
    { "SSLOnNoCRLSetEnv",set_server_string_slot,
      (void *)XtOffsetOf(SSLConfigRec,szOnNoCRLSetEnv),RSRC_CONF,TAKE1,
      "Set an environment variable if there is no CRL for the client certificate" },
    { "SSLNoV2",set_server_bool_slot,
      (void *)XtOffsetOf(SSLConfigRec,bNoV2),RSRC_CONF,NO_ARGS,
      "Don't use SSL version 2 (highly recommended)" },
    { NULL }
    };

module MODULE_VAR_EXPORT apache_ssl_module =
    {
    SSL_MODULE_STUFF,
    InitSSLServer,		/* initializer */
    CreateSSLDirConfig,		/* dir config creater */
    MergeSSLDirConfig,		/* dir merger --- default is to override */
    CreateSSLServerConfig,	/* server config */
    MergeSSLServerConfig,	/* merge server config */
    ssl_cmds,			/* command table */
    NULL,			/* handlers */
    NULL,			/* filename translation */
    FakeBasicAuth,		/* check_user_id */
    NULL,			/* check auth */
    CheckAccess,		/* check access */
    NULL,			/* type_checker */
    /*    SSLFixups,			fixups */
    NULL,			/* fixups */
    NULL,			/* logger */
    NULL,			/* header_parser */
    NULL,			/* child_init */
    NULL,			/* child_exit */
    NULL,			/* post_read_request */
    SSLSetupConnection2,	/* setup_connection */
    SSLAddCommonVars		/* add_common_vars */
    };
