/* Copyright 2001-2005 The Apache Software Foundation or its licensors, as
 * applicable.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "ssl_conf.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "connection.h"
#include "request.h"
#include "proc_rc.h"

#include "log.h"
#include "common.h"
#include "ticket.h"

/*  _________________________________________________________________
**
**  I/O Hooks
**  _________________________________________________________________
*/

/* This file is designed to be the bridge between OpenSSL and httpd.
 * However, we really don't expect anyone (let alone ourselves) to
 * remember what is in this file.  So, first, a quick overview.
 *
 * In this file, you will find:
 * - ssl_io_filter_input    (Apache input filter)
 * - ssl_io_filter_output   (Apache output filter)
 *
 * - bio_filter_in_*        (OpenSSL input filter)
 * - bio_filter_out_*       (OpenSSL output filter)
 *
 * The input chain is roughly:
 *
 * ssl_io_filter_input->ssl_io_input_read->SSL_read->...
 * ...->bio_filter_in_read->ap_get_brigade/next-httpd-filter
 *
 * In mortal terminology, we do the following:
 * - Receive a request for data to the SSL input filter
 * - Call a helper function once we know we should perform a read
 * - Call OpenSSL's SSL_read()
 * - SSL_read() will then call bio_filter_in_read
 * - bio_filter_in_read will then try to fetch data from the next httpd filter
 * - bio_filter_in_read will flatten that data and return it to SSL_read
 * - SSL_read will then decrypt the data
 * - ssl_io_input_read will then receive decrypted data as a char* and
 *   ensure that there were no read errors
 * - The char* is placed in a brigade and returned
 *
 * Since connection-level input filters in httpd need to be able to
 * handle AP_MODE_GETLINE calls (namely identifying LF-terminated strings),
 * ssl_io_input_getline which will handle this special case.
 *
 * Due to AP_MODE_GETLINE and AP_MODE_SPECULATIVE, we may sometimes have
 * 'leftover' decoded data which must be setaside for the next read.  That
 * is currently handled by the char_buffer_{read|write} functions.  So,
 * ssl_io_input_read may be able to fulfill reads without invoking
 * SSL_read().
 *
 * Note that the filter context of ssl_io_filter_input and bio_filter_in_*
 * are shared as bio_filter_in_ctx_t.
 *
 * Note that the filter is by choice limited to reading at most
 * AP_IOBUFSIZE (8192 bytes) per call.
 *
 */

typedef struct
{
  SSL                *pssl;
  BIO                *pbioRead;
  BIO                *pbioWrite;
  int                nobuffer; /* non-zero to prevent buffering */
} ssl_filter_ctx_t;

typedef struct 
{
  ssl_filter_ctx_t *filter_ctx;
  conn_rec *c;
  apr_size_t length;
  apr_size_t blen;
  apr_status_t rc;
} bio_filter_out_ctx_t;

typedef struct 
{
  int length;
  char *value;
} char_buffer_t;

typedef struct 
{
  SSL *ssl;
  BIO *bio_out;
  conn_rec *c;
  apr_status_t rc;
  char_buffer_t cbuf;
  apr_pool_t *pool;
  ssl_filter_ctx_t *filter_ctx;
} bio_filter_in_ctx_t;

static bio_filter_out_ctx_t *
bio_filter_out_ctx_new(ssl_filter_ctx_t *filter_ctx,
                       conn_rec *c)
{
  bio_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx));
  
  outctx->filter_ctx = filter_ctx;
  outctx->c = c;
  outctx->blen = 0;
  outctx->length = 0;
  
  return outctx;
}

static apr_status_t 
ssl_io_filter_cleanup(void *data)
{
  ssl_filter_ctx_t *filter_ctx = data;
  
  if (filter_ctx->pssl) 
    {
      conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
      SSLConnRec *sslconn = myConnConfig(c);
      
      SSL_free(filter_ctx->pssl);
      sslconn->ssl = filter_ctx->pssl = NULL;
    }
  
  return APR_SUCCESS;
}

static int 
bio_filter_create(BIO *bio)
{
  bio->shutdown = 1;
  bio->init = 1;
  bio->num = -1;
  bio->ptr = NULL;
  
  return 1;
}

static int
bio_filter_destroy(BIO *bio)
{
  if (bio == NULL)
    return 0;

  /* nothing to free here.
   * apache will destroy the bucket brigade for us
   */
  return 1;
}

/*
 * this is the function called by SSL_read()
 */
static int bio_filter_in_read(BIO *bio, char *in, int inlen)
{
  apr_size_t inl = inlen;
  bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)(bio->ptr);
  conn_rec *c = inctx->c;

  inctx->rc = APR_SUCCESS;
  
  /* OpenSSL catches this case, so should we. */
  if (!in)
    return 0;
  
  BIO_clear_retry_flags (bio);

  inctx->rc = rc_socket_recv (c->sock, in, &inl);
  if (inctx->rc == APR_SUCCESS)
    return (int)inl;
    
  if (APR_STATUS_IS_EAGAIN(inctx->rc)
      || APR_STATUS_IS_EINTR(inctx->rc)) 
    {
      BIO_set_retry_read(bio);
      return (int)inl;
    }
  
  if (APR_STATUS_IS_EOF(inctx->rc) && inl) 
    {
      /* Provide the results of this read pass,
       * without resetting the BIO retry_read flag
       */
      return (int)inl;
    }
  
  return -1;
}

static BIO_METHOD bio_filter_in_method = 
  {
    BIO_TYPE_MEM,
    "APR input filter",
    NULL,                       /* write is never called */
    bio_filter_in_read,
    NULL,                       /* puts is never called */
    NULL,                       /* gets is never called */
    NULL,                       /* ctrl is never called */
    bio_filter_create,
    bio_filter_destroy,
#ifdef OPENSSL_VERSION_NUMBER
    NULL /* sslc does not have the callback_ctrl field */
#endif
  };

static int
bio_filter_out_write (BIO *bio, const char *in, int inl)
{
  bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr);
  
  /* when handshaking we'll have a small number of bytes.
   * max size SSL will pass us here is about 16k.
   * (16413 bytes to be exact)
   */
  BIO_clear_retry_flags(bio);
 
  {
    rc_status_t rv;
    int inlen = inl;
    
    rv = rc_socket_send (outctx->c->sock, (const char *) in, &inlen);
    if (inlen != inl)
      {
        rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, "Can't write");
        return -1;
      }
  }
  
  return inl;
}

static int
bio_filter_out_read(BIO *bio, char *out, int outl)
{
  /* this is never called */
  return -1;
}

static int
bio_filter_out_gets(BIO *bio, char *buf, int size)
{
  /* this is never called */
  return -1;
}

static int
bio_filter_out_puts(BIO *bio, const char *str)
{
  /* this is never called */
  return -1;
}

static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
  long ret = 1;
  
  bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr);
  
  switch (cmd) 
    {
    case BIO_CTRL_RESET:
      outctx->blen = outctx->length = 0;
      break;
    case BIO_CTRL_EOF:
      ret = (long)((outctx->blen + outctx->length) == 0);
      break;
    case BIO_C_SET_BUF_MEM_EOF_RETURN:
      outctx->blen = outctx->length = (apr_size_t)num;
      break;
    case BIO_CTRL_INFO:
      ret = (long)(outctx->blen + outctx->length);
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "Under construction1");
#if 0
      if (ptr) 
        {
          pptr = (char **)ptr;
          *pptr = (char *)&(outctx->buffer[0]);
        }
#endif
      break;
    case BIO_CTRL_GET_CLOSE:
      ret = (long)bio->shutdown;
      break;
    case BIO_CTRL_SET_CLOSE:
      bio->shutdown = (int)num;
      break;
    case BIO_CTRL_WPENDING:
      ret = 0L;
      break;
    case BIO_CTRL_PENDING:
      ret = (long)(outctx->blen + outctx->length);
      break;
    case BIO_CTRL_FLUSH:
      break;
    case BIO_CTRL_DUP:
      ret = 1;
      break;
      /* N/A */
    case BIO_C_SET_BUF_MEM:
    case BIO_C_GET_BUF_MEM_PTR:
      /* we don't care */
    case BIO_CTRL_PUSH:
    case BIO_CTRL_POP:
    default:
      ret = 0;
      break;
    }
  
  return ret;
}

static BIO_METHOD bio_filter_out_method = 
  {
    BIO_TYPE_MEM,
    "APR output filter",
    bio_filter_out_write,
    bio_filter_out_read,     /* read is never called */
    bio_filter_out_puts,     /* puts is never called */
    bio_filter_out_gets,     /* gets is never called */
    bio_filter_out_ctrl,
    bio_filter_create,
    bio_filter_destroy,
#ifdef OPENSSL_VERSION_NUMBER
    NULL /* sslc does not have the callback_ctrl field */
#endif
  };

static apr_status_t
ssl_io_input_read(bio_filter_in_ctx_t *inctx,
                  char *buf,
                  apr_size_t *len)
{
  apr_size_t wanted = *len;
  apr_size_t bytes = 0;
  int rc;
  
  *len = 0;

  while (1) 
    {
      if (!inctx->filter_ctx->pssl) 
        {
          /* Ensure a non-zero error code is returned */
          if (inctx->rc == APR_SUCCESS)
            inctx->rc = APR_EGENERAL;
          break;
        }

      /* SSL_read may not read because we haven't taken enough data
       * from the stack.  This is where we want to consider all of
       * the blocking and SPECULATIVE semantics
       */
      rc = SSL_read (inctx->filter_ctx->pssl, buf + bytes, wanted - bytes);
      if (rc > 0) 
        {
          *len += rc;
          return inctx->rc;
        }
      else if (rc == 0) 
        {
          /* If EAGAIN, we will loop given a blocking read,
           * otherwise consider ourselves at EOF.
           */
          if (APR_STATUS_IS_EAGAIN(inctx->rc)
              || APR_STATUS_IS_EINTR(inctx->rc)) 
            {
              /* Already read something, return APR_SUCCESS instead.
               * On win32 in particular, but perhaps on other kernels,
               * a blocking call isn't 'always' blocking.
               */
              if (*len > 0) 
                {
                  inctx->rc = APR_SUCCESS;
                  break;
                }
            }
          else 
            {
              if (*len > 0)
                inctx->rc = APR_SUCCESS;
              else
                inctx->rc = APR_EOF;
              break;
            }
        }
      else /* (rc < 0) */ 
        {
          int ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc);
          conn_rec *c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl);
          
          if (ssl_err == SSL_ERROR_WANT_READ) 
            {
              /*
               * If OpenSSL wants to read more, and we were nonblocking,
               * report as an EAGAIN.  Otherwise loop, pulling more
               * data from network filter.
               *
               * (This is usually the case when the client forces an SSL
               * renegotation which is handled implicitly by OpenSSL.)
               */
              inctx->rc = APR_EAGAIN;
              
              if (*len > 0) 
                {
                  inctx->rc = APR_SUCCESS;
                  break;
                }              
              continue;  /* Blocking and nothing yet?  Try again. */
            }
          else if (ssl_err == SSL_ERROR_SYSCALL) 
            {
              if (APR_STATUS_IS_EAGAIN(inctx->rc)
                  || APR_STATUS_IS_EINTR(inctx->rc)) 
                {
                  /* Already read something, return APR_SUCCESS instead. */
                  if (*len > 0) 
                    {
                      inctx->rc = APR_SUCCESS;
                      break;
                    }
                  continue;  /* Blocking and nothing yet?  Try again. */
                }
              else
                rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                          "SSL input filter read failed.");
            }
          else /* if (ssl_err == SSL_ERROR_SSL) */ 
            {
              /*
               * Log SSL errors and any unexpected conditions.
               */
              rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                        "SSL library error %d reading data", ssl_err);
              ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_INFO, c->ts->arg);
              
            }
          if (inctx->rc == APR_SUCCESS)
            inctx->rc = APR_EGENERAL;
          break;
        }
    }
  return inctx->rc;
}

static void 
ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
                        SSL *ssl)
{
  bio_filter_in_ctx_t *inctx;
  
  inctx = apr_palloc(c->pool, sizeof(*inctx));
  
  filter_ctx->pbioRead = BIO_new(&bio_filter_in_method);
  filter_ctx->pbioRead->ptr = (void *)inctx;
  
  inctx->ssl = ssl;
  inctx->bio_out = filter_ctx->pbioWrite;
  inctx->c = c;
  inctx->rc = APR_SUCCESS;
  //inctx->mode = AP_MODE_READBYTES;
  inctx->cbuf.length = 0;
  //inctx->bb = apr_brigade_create (c->pool, c->bucket_alloc);
  //inctx->block = APR_BLOCK_READ;
  inctx->pool = c->pool;
  inctx->filter_ctx = filter_ctx;
}

/*  _________________________________________________________________
**
**  I/O Data Debugging
**  _________________________________________________________________
*/

#define DUMP_WIDTH 16

static void 
ssl_io_data_dump(struct rovm *srvr,
                 MODSSL_BIO_CB_ARG_TYPE *s,
                 long len)
{
  char buf[256];
  char tmp[64];
  int i, j, rows, trunc;
  unsigned char ch;
  
  trunc = 0;
  for(; (len > 0) && ((s[len-1] == ' ') || (s[len-1] == '\0')); len--)
    trunc++;
  rows = (len / DUMP_WIDTH);
  if ((rows * DUMP_WIDTH) < len)
    rows++;

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
            "+-------------------------------------------------------------------------+");
  for(i = 0 ; i< rows; i++) 
    {
      apr_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH);
      apr_cpystrn(buf, tmp, sizeof(buf));
      for (j = 0; j < DUMP_WIDTH; j++) 
        {
          if (((i * DUMP_WIDTH) + j) >= len)
            apr_cpystrn(buf+strlen(buf), "   ", sizeof(buf)-strlen(buf));
          else 
            {
              ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
              apr_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' ');
              apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
            }
        }
      apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
      for (j = 0; j < DUMP_WIDTH; j++) 
        {
          if (((i * DUMP_WIDTH) + j) >= len)
            apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
          else 
            {
              ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
              apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
              apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
            }
        }
      apr_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf));
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                "%s", buf);
    }
    if (trunc > 0)
      rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                "| %04ld - <SPACES/NULS>", len + trunc);
    rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
              "+-------------------------------------------------------------------------+");
    return;
}

long 
ssl_io_data_cb(BIO *bio, int cmd,
               MODSSL_BIO_CB_ARG_TYPE *argp,
               int argi, long argl, long rc)
{
  SSL *ssl;
  conn_rec *c;
  struct rovm *r;
  
  if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL)
    return rc;
  if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL)
    return rc;
  r = c->ts->arg;

  if (cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
      || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) 
    {
      if (rc >= 0) 
        {
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                    "%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s",
                    SSL_LIBRARY_NAME,
                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
                    rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"),
                    bio, argp,
                    (argp != NULL ? "(BIO dump follows)" : "(Oops, no memory buffer?)"));
          if (argp != NULL)
            ssl_io_data_dump(r, argp, rc);
        }
      else 
        {
          rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, 
                    "%s: I/O error, %d bytes expected to %s on BIO#%pp [mem: %pp]",
                    SSL_LIBRARY_NAME, argi,
                    (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
                    bio, argp);
        }
    }
  return rc;
}

void
ssl_io_filter_init (conn_rec *c, SSL *ssl)
{
  ssl_filter_ctx_t *filter_ctx;
  
  filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t));
  
  filter_ctx->nobuffer        = 0;
  
  filter_ctx->pbioWrite       = BIO_new(&bio_filter_out_method);
  filter_ctx->pbioWrite->ptr  = (void *)bio_filter_out_ctx_new(filter_ctx, c);
  
  ssl_io_input_add_filter (filter_ctx, c, ssl);
  
  SSL_set_bio(ssl, filter_ctx->pbioRead, filter_ctx->pbioWrite);
  filter_ctx->pssl            = ssl;
  
  mp_cleanup_register (c->pool, (void*)filter_ctx,
                       ssl_io_filter_cleanup, apr_pool_cleanup_null);
  
  if (CONF_LOGLEVEL (ROVM_CONF (c->ts->arg)) >= ROVMLOG_DEBUG) 
    {
      BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb);
      BIO_set_callback_arg(SSL_get_rbio(ssl), (void *)ssl);
    }

  c->filter_ctx = (void *) filter_ctx;

  return;
}

/*
 *  Close the SSL part of the socket connection
 *  (called immediately _before_ the socket is closed)
 *  or called with
 */
static apr_status_t
ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx,
                       conn_rec *c,
                       int abortive)
{
  SSL *ssl = filter_ctx->pssl;
  const char *type = "";
  SSLConnRec *sslconn = myConnConfig(c);
  int shutdown_type;
  
  if (!ssl)
    return APR_SUCCESS;

  /*
   * Now close the SSL layer of the connection. We've to take
   * the TLSv1 standard into account here:
   *
   * | 7.2.1. Closure alerts
   * |
   * | The client and the server must share knowledge that the connection is
   * | ending in order to avoid a truncation attack. Either party may
   * | initiate the exchange of closing messages.
   * |
   * | close_notify
   * |     This message notifies the recipient that the sender will not send
   * |     any more messages on this connection. The session becomes
   * |     unresumable if any connection is terminated without proper
   * |     close_notify messages with level equal to warning.
   * |
   * | Either party may initiate a close by sending a close_notify alert.
   * | Any data received after a closure alert is ignored.
   * |
   * | Each party is required to send a close_notify alert before closing
   * | the write side of the connection. It is required that the other party
   * | respond with a close_notify alert of its own and close down the
   * | connection immediately, discarding any pending writes. It is not
   * | required for the initiator of the close to wait for the responding
   * | close_notify alert before closing the read side of the connection.
   *
   * This means we've to send a close notify message, but haven't to wait
   * for the close notify of the client. Actually we cannot wait for the
   * close notify of the client because some clients (including Netscape
   * 4.x) don't send one, so we would hang.
   */
  
  /*
   * exchange close notify messages, but allow the user
   * to force the type of handshake via SetEnvIf directive
   */
  if (abortive) 
    {
      shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
      type = "abortive";
    }
  else switch (sslconn->shutdown_type) 
         {
         case SSL_SHUTDOWN_TYPE_UNCLEAN:
           /* perform no close notify handshake at all
              (violates the SSL/TLS standard!) */
           shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
           type = "unclean";
           break;
         case SSL_SHUTDOWN_TYPE_ACCURATE:
           /* send close notify and wait for clients close notify
              (standard compliant, but usually causes connection hangs) */
           shutdown_type = 0;
           type = "accurate";
           break;
         default:
           /*
            * case SSL_SHUTDOWN_TYPE_UNSET:
            * case SSL_SHUTDOWN_TYPE_STANDARD:
            */
           /* send close notify, but don't wait for clients close notify
              (standard compliant and safe, so it's the DEFAULT!) */
           shutdown_type = SSL_RECEIVED_SHUTDOWN;
           type = "standard";
           break;
         }
  
  SSL_set_shutdown (ssl, shutdown_type);
  SSL_smart_shutdown (ssl);
  
  /* and finally log the fact that we've closed the connection */
  if (CONF_LOGLEVEL (ROVM_CONF (c->ts->arg)) >= ROVMLOG_INFO) 
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "Connection closed to child %ld with %s shutdown "
                "(server %s)",
                123, type, ssl_util_vhostid(c->pool, c->ts->arg));
    }
  
  /* deallocate the SSL connection */
  if (sslconn->client_cert) 
    {
      X509_free(sslconn->client_cert);
      sslconn->client_cert = NULL;
    }
  SSL_free(ssl);
  sslconn->ssl = NULL;
  filter_ctx->pssl = NULL; /* so filters know we've been shutdown */
  
  if (abortive) 
    {
      /* prevent any further I/O */
      c->aborted = 1;
    }
  
  return APR_SUCCESS;
}

/*
 * The hook is NOT registered with ap_hook_process_connection. Instead, it is
 * called manually from the churn () before it tries to read any data.
 * There is some problem if I accept conn_rec *. Still investigating..
 * Adv. if conn_rec * can be accepted is we can hook this function using the
 * ap_hook_process_connection hook.
 */
static int 
ssl_io_filter_connect (ssl_filter_ctx_t *filter_ctx)
{
  conn_rec *c         = (conn_rec *)SSL_get_app_data (filter_ctx->pssl);
  SSLConnRec *sslconn = myConnConfig(c);
  SSLSrvConfigRec *sc = mySrvConfig(c->ts->arg);
  X509 *cert;
  int n;
  int ssl_err;
  long verify_result;
  
  if (SSL_is_init_finished(filter_ctx->pssl))
    return APR_SUCCESS;

  if ((n = SSL_accept(filter_ctx->pssl)) <= 0) 
    {
      bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)
        (filter_ctx->pbioRead->ptr);
      bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)
        (filter_ctx->pbioWrite->ptr);
      ssl_err = SSL_get_error(filter_ctx->pssl, n);
      
      if (ssl_err == SSL_ERROR_ZERO_RETURN) 
        {
          /*
           * The case where the connection was closed before any data
           * was transferred. That's not a real error and can occur
           * sporadically with some clients.
           */
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL handshake stopped: connection was closed");
        }
      else if (ssl_err == SSL_ERROR_WANT_READ) 
        {
          /*
           * This is in addition to what was present earlier. It is
           * borrowed from openssl_state_machine.c [mod_tls].
           * TBD.
           */
          outctx->rc = APR_EAGAIN;
          return SSL_ERROR_WANT_READ;
        }
      else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL &&
               ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) 
        {
          /*
           * The case where OpenSSL has recognized a HTTP request:
           * This means the client speaks plain HTTP on our HTTPS port.
           * ssl_io_filter_error will disable the ssl filters when it
           * sees this status code.
           */
          return -1;
        }
      else if (ssl_err == SSL_ERROR_SYSCALL) 
        {
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL handshake interrupted by system "
                    "[Hint: Stop button pressed in browser?!]");
        }
      else /* if (ssl_err == SSL_ERROR_SSL) */ 
        {
          /*
           * Log SSL errors and any unexpected conditions.
           */
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL library error %d in handshake "
                    "(server %s)", ssl_err,
                    ssl_util_vhostid(c->pool, c->ts->arg));
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_INFO, c->ts->arg);
        }
      if (inctx->rc == APR_SUCCESS)
        inctx->rc = APR_EGENERAL;

      return ssl_filter_io_shutdown (filter_ctx, c, 1);
    }
  
  /*
   * Check for failed client authentication
   */
  verify_result = SSL_get_verify_result(filter_ctx->pssl);
  
  if ((verify_result != X509_V_OK) ||
      sslconn->verify_error)
    {
      if (ssl_verify_error_is_optional(verify_result) &&
          (sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
        {
          /* leaving this log message as an error for the moment,
           * according to the mod_ssl docs:
           * "level optional_no_ca is actually against the idea
           *  of authentication (but can be used to establish
           * SSL test pages, etc.)"
             * optional_no_ca doesn't appear to work as advertised
             * in 1.x
             */
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL client authentication failed, "
                    "accepting certificate based on "
                    "\"SSLVerifyClient optional_no_ca\" "
                    "configuration");
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_INFO, c->ts->arg);
        }
      else 
        {
          const char *error = sslconn->verify_error ?
            sslconn->verify_error :
            X509_verify_cert_error_string(verify_result);
          
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL client authentication failed: %s",
                    error ? error : "unknown");
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_INFO, c->ts->arg);
          
          return ssl_filter_io_shutdown(filter_ctx, c, 1);
        }
    }
  
  /*
   * Remember the peer certificate's DN
   */
  if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) 
    {
      if (sslconn->client_cert) 
        X509_free(sslconn->client_cert);

      sslconn->client_cert = cert;
      sslconn->client_dn = NULL;
    }
  
  /*
   * Make really sure that when a peer certificate
   * is required we really got one... (be paranoid)
   */
  if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) &&
      !sslconn->client_cert)
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "No acceptable peer certificate available");
      
      return ssl_filter_io_shutdown(filter_ctx, c, 1);
    }
  
  return APR_SUCCESS;
}

int
ssl_io_filter_output (conn_rec *c)
{
  apr_status_t status = APR_SUCCESS;
  ssl_filter_ctx_t *filter_ctx;

  filter_ctx = (ssl_filter_ctx_t *) c->filter_ctx;

  if (!filter_ctx->pssl)
    return 0;

  if ((status = ssl_io_filter_connect (filter_ctx)) != APR_SUCCESS)
    return -1;

  filter_ctx->nobuffer = 1;
  status = ssl_filter_io_shutdown (filter_ctx, c, 0);
  if (status != APR_SUCCESS) 
    {
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "SSL filter error shutting down I/O");
    }

  return status;
}

int
ssl_filter_shutdown (conn_rec *c)
{
  return ssl_io_filter_output (c);
}

rc_status_t
ssl_filter_write(conn_rec *c,
                 const char *data,
                 apr_size_t len)
{
  ssl_filter_ctx_t *filter_ctx = c->filter_ctx;
  bio_filter_out_ctx_t *outctx;
  int res;

  if (c->aborted)
    return APR_ECONNABORTED;

  /* write SSL */
  if (filter_ctx->pssl == NULL)
    return APR_EGENERAL;

  outctx = (bio_filter_out_ctx_t *) filter_ctx->pbioWrite->ptr;
  res = SSL_write (filter_ctx->pssl, (unsigned char *)data, len);
  
  if (res < 0) 
    {
      int ssl_err = SSL_get_error(filter_ctx->pssl, res);
      conn_rec *c = (conn_rec*)SSL_get_app_data(outctx->filter_ctx->pssl);
      
      if (ssl_err == SSL_ERROR_WANT_WRITE) 
        {
          /*
           * If OpenSSL wants to write more, and we were nonblocking,
           * report as an EAGAIN.  Otherwise loop, pushing more
           * data at the network filter.
           *
           * (This is usually the case when the client forces an SSL
           * renegotation which is handled implicitly by OpenSSL.)
           */
          outctx->rc = APR_EAGAIN;
        }
      else if (ssl_err == SSL_ERROR_SYSCALL) 
        {
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL output filter write failed.");
        }
      else /* if (ssl_err == SSL_ERROR_SSL) */ 
        {
          /* Log SSL errors.  */
          rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                    "SSL library error %d writing data", ssl_err);
          ssl_log_ssl_error(ROVMLOG_MARK, ROVMLOG_INFO, c->ts->arg);
        }
      if (outctx->rc == APR_SUCCESS)
        outctx->rc = APR_EGENERAL;
    }
  else if ((apr_size_t)res != len) 
    {
      char *reason = "reason unknown";
      
      /* XXX: probably a better way to determine this */
      if (SSL_total_renegotiations (filter_ctx->pssl)) 
        {
          reason = "likely due to failed renegotiation";
        }
      
      rovm_log (NULL, ROVMLOG_INFO, ROVMLOG_MARK, 
                "failed to write %" APR_SSIZE_T_FMT
                " of %" APR_SIZE_T_FMT " bytes (%s)",
                len - (apr_size_t)res, len, reason);
      
      outctx->rc = APR_EGENERAL;
    }

  return outctx->rc;
}

int
ssl_filter_read (conn_rec *c, char *data, apr_size_t *len)
{
  ssl_filter_ctx_t *filter_ctx;
  bio_filter_in_ctx_t *inctx;
  apr_status_t status;
  
  if (c->aborted)
    return APR_ECONNABORTED;
  
  filter_ctx = (ssl_filter_ctx_t *) c->filter_ctx;
  inctx = (bio_filter_in_ctx_t *) filter_ctx->pbioRead->ptr;
  
  /* XXX: we could actually move ssl_io_filter_connect to an
   * ap_hook_process_connection but would still need to call it for
   * AP_MODE_INIT for protocols that may upgrade the connection
   * rather than have SSLEngine On configured.
   */
  if ((status = ssl_io_filter_connect (inctx->filter_ctx)) != APR_SUCCESS)
    return -1;

  if (ssl_io_input_read (inctx, data, len))
    return -1;

  return 0;
}

int
ssl_test (conn_rec *c)
{
  char buf[256];
  int len = 3;
  ssl_filter_ctx_t *filter_ctx;
  bio_filter_in_ctx_t *inctx;
  apr_status_t status;
  memset (buf, '\0', 256);

  if (c->aborted)
    return APR_ECONNABORTED;

  filter_ctx = (ssl_filter_ctx_t *) c->filter_ctx;
  inctx = (bio_filter_in_ctx_t *) filter_ctx->pbioRead->ptr;
  
  /* XXX: we could actually move ssl_io_filter_connect to an
   * ap_hook_process_connection but would still need to call it for
   * AP_MODE_INIT for protocols that may upgrade the connection
   * rather than have SSLEngine On configured.
   */
  if ((status = ssl_io_filter_connect (inctx->filter_ctx)) != APR_SUCCESS)
    return -1;

  ssl_io_input_read (inctx, buf, &len);

  rovm_log (NULL, ROVMLOG_DEBUG, ROVMLOG_MARK, "Buffer Contents = [%s]", buf);

  return 0;
}
