text: /*                      _             _
  **  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
  ** | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
  ** | | | | | | (_) | (_| |   \__ \__ \ |  www.modssl.org
  ** |_| |_| |_|\___/ \__,_|___|___/___/_|  ftp.modssl.org
  **                      |_____|
  **  ssl_engine_io.c
  **  I/O Functions
  */
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2000-2002 The Apache Software Foundation.  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. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``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 THE APACHE SOFTWARE FOUNDATION OR
   * ITS 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.
   * ====================================================================
   */
                               /* ``MY HACK: This universe.
                                    Just one little problem:
                                    core keeps dumping.''
                                              -- Unknown    */
  #include "mod_ssl.h"
  
  /*  _________________________________________________________________
  **
  **  I/O Hooks
  **  _________________________________________________________________
  */
  
  /* XXX THIS STUFF NEEDS A MAJOR CLEANUP -RSE XXX */
  
  /* this custom BIO allows us to hook SSL_write directly into 
   * an apr_bucket_brigade and use transient buckets with the SSL
   * malloc-ed buffer, rather than copying into a mem BIO.
   * also allows us to pass the brigade as data is being written
   * rather than buffering up the entire response in the mem BIO.
   *
   * when SSL needs to flush (e.g. SSL_accept()), it will call BIO_flush()
   * which will trigger a call to bio_bucket_ctrl() -> BIO_bucket_flush().
   * so we only need to flush the output ourselves if we receive an
   * EOS or FLUSH bucket. this was not possible with the mem BIO where we
   * had to flush all over the place not really knowing when it was required
   * to do so.
   */
  
  typedef struct {
      SSLFilterRec *frec;
      conn_rec *c;
      apr_bucket_brigade *bb;
      apr_size_t length;
      char buffer[AP_IOBUFSIZE];
      apr_size_t blen;
  } BIO_bucket_t;
  
  static BIO_bucket_t *BIO_bucket_new(SSLFilterRec *frec, conn_rec *c)
  {
      BIO_bucket_t *b = apr_palloc(c->pool, sizeof(*b));
  
      b->frec = frec;
      b->c = c;
      b->bb = apr_brigade_create(c->pool, c->bucket_alloc);
      b->blen = 0;
      b->length = 0;
  
      return b;
  }
  
  #define BIO_bucket_ptr(bio) (BIO_bucket_t *)bio->ptr
  
  static int BIO_bucket_flush(BIO *bio)
  {
      BIO_bucket_t *b = BIO_bucket_ptr(bio);
      apr_bucket *e;
  
      if (!(b->blen || b->length)) {
          return APR_SUCCESS;
      }
  
      if (b->blen) {
          e = apr_bucket_transient_create(b->buffer, b->blen,
                                          b->bb->bucket_alloc);
          /* we filled this buffer first so add it to the 
           * head of the brigade
           */
          APR_BRIGADE_INSERT_HEAD(b->bb, e);
          b->blen = 0;
      }
  
      b->length = 0;
      e = apr_bucket_flush_create(b->bb->bucket_alloc);
      APR_BRIGADE_INSERT_TAIL(b->bb, e);
  
      return ap_pass_brigade(b->frec->pOutputFilter->next, b->bb);
  }
  
  static int bio_bucket_new(BIO *bio)
  {
      bio->shutdown = 1;
      bio->init = 1;
      bio->num = -1;
      bio->ptr = NULL;
  
      return 1;
  }
  
  static int bio_bucket_free(BIO *bio)
  {
      if (bio == NULL) {
          return 0;
      }
  
      /* nothing to free here.
       * apache will destroy the bucket brigade for us
       */
      return 1;
  }
  	
  static int bio_bucket_read(BIO *bio, char *out, int outl)
  {
      /* this is never called */
      return -1;
  }
  
  static int bio_bucket_write(BIO *bio, const char *in, int inl)
  {
      BIO_bucket_t *b = BIO_bucket_ptr(bio);
  
      /* 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);
  
      if (!b->length && (inl + b->blen < sizeof(b->buffer))) {
          /* the first two SSL_writes (of 1024 and 261 bytes)
           * need to be in the same packet (vec[0].iov_base)
           */
          /* XXX: could use apr_brigade_write() to make code look cleaner
           * but this way we avoid the malloc(APR_BUCKET_BUFF_SIZE)
           * and free() of it later
           */
          memcpy(&b->buffer[b->blen], in, inl);
          b->blen += inl;
      }
      else {
          /* pass along the encrypted data
           * need to flush since we're using SSL's malloc-ed buffer 
           * which will be overwritten once we leave here
           */
          apr_bucket *bucket = apr_bucket_transient_create(in, inl,
                                                           b->bb->bucket_alloc);
  
          b->length += inl;
          APR_BRIGADE_INSERT_TAIL(b->bb, bucket);
  
          BIO_bucket_flush(bio);
      }
  
      return inl;
  }
  
  static long bio_bucket_ctrl(BIO *bio, int cmd, long num, void *ptr)
  {
      long ret = 1;
      char **pptr;
  
      BIO_bucket_t *b = BIO_bucket_ptr(bio);
  
      switch (cmd) {
        case BIO_CTRL_RESET:
          b->blen = b->length = 0;
          break;
        case BIO_CTRL_EOF:
          ret = (long)((b->blen + b->length) == 0);
          break;
        case BIO_C_SET_BUF_MEM_EOF_RETURN:
          b->blen = b->length = (apr_size_t)num;
          break;
        case BIO_CTRL_INFO:
          ret = (long)(b->blen + b->length);
          if (ptr) {
              pptr = (char **)ptr;
              *pptr = (char *)&(b->buffer[0]);
          }
          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)(b->blen + b->length);
          break;
        case BIO_CTRL_FLUSH:
          ret = (BIO_bucket_flush(bio) == APR_SUCCESS);
          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 int bio_bucket_gets(BIO *bio, char *buf, int size)
  {
      /* this is never called */
      return -1;
  }
  
  static int bio_bucket_puts(BIO *bio, const char *str)
  {
      /* this is never called */
      return -1;
  }
  
  static BIO_METHOD bio_bucket_method = {
      BIO_TYPE_MEM,
      "APR bucket brigade",
      bio_bucket_write,
      bio_bucket_read,
      bio_bucket_puts,
      bio_bucket_gets,
      bio_bucket_ctrl,
      bio_bucket_new,
      bio_bucket_free,
  #ifdef OPENSSL_VERSION_NUMBER
      NULL /* sslc does not have the callback_ctrl field */
  #endif
  };
  
  static BIO_METHOD *BIO_s_bucket(void)
  {
      return &bio_bucket_method;
  }
  
  typedef struct {
      int length;
      char *value;
  } char_buffer_t;
  
  typedef struct {
      SSL *ssl;
      BIO *wbio;
      ap_filter_t *f;
      apr_status_t rc;
      ap_input_mode_t mode;
      apr_read_type_e block;
      apr_bucket_brigade *bb;
      apr_bucket *bucket;
      char_buffer_t cbuf;
  } BIO_bucket_in_t;
  
  typedef struct {
      BIO_bucket_in_t inbio;
      char_buffer_t cbuf;
      apr_pool_t *pool;
      char buffer[AP_IOBUFSIZE];
      SSLFilterRec *frec;
  } ssl_io_input_ctx_t;
  
  /*
   * this char_buffer api might seem silly, but we don't need to copy
   * any of this data and we need to remember the length.
   */
  static int char_buffer_read(char_buffer_t *buffer, char *in, int inl)
  {
      if (!buffer->length) {
          return 0;
      }
  
      if (buffer->length > inl) {
          /* we have have enough to fill the caller's buffer */
          memcpy(in, buffer->value, inl);
          buffer->value += inl;
          buffer->length -= inl;
      }
      else {
          /* swallow remainder of the buffer */
          memcpy(in, buffer->value, buffer->length);
          inl = buffer->length;
          buffer->value = NULL;
          buffer->length = 0;
      }
  
      return inl;
  }
  
  static int char_buffer_write(char_buffer_t *buffer, char *in, int inl)
  {
      buffer->value = in;
      buffer->length = inl;
      return inl;
  }
  
  /*
   * this is the function called by SSL_read()
   */
  #define BIO_bucket_in_ptr(bio) (BIO_bucket_in_t *)bio->ptr
  
  static int bio_bucket_in_read(BIO *bio, char *in, int inl)
  {
      BIO_bucket_in_t *inbio = BIO_bucket_in_ptr(bio);
      SSLConnRec *sslconn = myConnConfig(inbio->f->c);
      int len = 0;
  
      /* XXX: flush here only required for SSLv2;
       * OpenSSL calls BIO_flush() at the appropriate times for
       * the other protocols.
       */
      if ((SSL_version(inbio->ssl) == SSL2_VERSION) || sslconn->is_proxy) {
          BIO_bucket_flush(inbio->wbio);
      }
  
      inbio->rc = APR_SUCCESS;
      
      /* first use data already read from socket if any */
      if ((len = char_buffer_read(&inbio->cbuf, in, inl))) {
          if ((len <= inl) || inbio->mode == AP_MODE_GETLINE) {
              return len;
          }
          inl -= len;
      }
  
      while (1) {
          const char *buf;
          apr_size_t buf_len = 0;
  
          if (inbio->bucket) {
              /* all of the data in this bucket has been read,
               * so we can delete it now.
               */
              apr_bucket_delete(inbio->bucket);
              inbio->bucket = NULL;
          }
  
          if (APR_BRIGADE_EMPTY(inbio->bb)) {
              /* We will always call with READBYTES even if the user wants
               * GETLINE.
               */
              inbio->rc = ap_get_brigade(inbio->f->next, inbio->bb,
                                         AP_MODE_READBYTES, inbio->block, 
                                         inl);
  
              if ((inbio->rc != APR_SUCCESS) || APR_BRIGADE_EMPTY(inbio->bb))
              {
                  break;
              }
          }
  
          inbio->bucket = APR_BRIGADE_FIRST(inbio->bb);
  
          inbio->rc = apr_bucket_read(inbio->bucket,
                                      &buf, &buf_len, inbio->block);
  
          if (inbio->rc != APR_SUCCESS) {
              apr_bucket_delete(inbio->bucket);
              inbio->bucket = NULL;
              return len;
          }
  
          if (buf_len) {
              /* Protected against len > MAX_INT 
               */
              if ((len + (int)buf_len) >= inl || (int)buf_len < 0) {
                  /* we have enough to fill the buffer.
                   * append if we have already written to the buffer.
                   */
                  int nibble = inl - len;
                  char *value = (char *)buf+nibble;
  
                  int length = buf_len - nibble;
                  memcpy(in + len, buf, nibble);
  
                  char_buffer_write(&inbio->cbuf, value, length);
                  len += nibble;
  
                  break;
              }
              else {
                  /* not enough data,
                   * save what we have and try to read more.
                   */
                  memcpy(in + len, buf, buf_len);
                  len += buf_len;
              }
          }
  
          if (inbio->mode == AP_MODE_GETLINE) {
              /* only read from the socket once in getline mode.
               * since callers buffer size is likely much larger than
               * the request headers.  caller can always come back for more
               * if first read didn't get all the headers.
               */
              break;
          }
      }
  
      return len;
  }
  
  static BIO_METHOD bio_bucket_in_method = {
      BIO_TYPE_MEM,
      "APR input bucket brigade",
      NULL,                       /* write is never called */
      bio_bucket_in_read,
      NULL,                       /* puts is never called */
      NULL,                       /* gets is never called */
      NULL,                       /* ctrl is never called */
      bio_bucket_new,
      bio_bucket_free,
  #ifdef OPENSSL_VERSION_NUMBER
      NULL /* sslc does not have the callback_ctrl field */
  #endif
  };
  
  static BIO_METHOD *BIO_s_in_bucket(void)
  {
      return &bio_bucket_in_method;
  }
  
  static const char ssl_io_filter[] = "SSL/TLS Filter";
  
  static int ssl_io_hook_read(SSL *ssl, char *buf, int len)
  {
      int rc;
  
      if (ssl == NULL) {
          return -1;
      }
  
      rc = SSL_read(ssl, buf, len);
  
      if (rc <= 0) {
          int ssl_err = SSL_get_error(ssl, rc);
  
          if (ssl_err == SSL_ERROR_WANT_READ) {
              /*
               * Simulate an EINTR in case OpenSSL wants to read more.
               * (This is usually the case when the client forces an SSL
               * renegotation which is handled implicitly by OpenSSL.)
               */
              errno = EINTR;
              rc = 0; /* non fatal error */
          }
          else if (ssl_err == SSL_ERROR_SSL) {
              /*
               * Log SSL errors
               */
              conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
              ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                      "SSL error on reading data");
              ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
          }
      }
  
      return rc;
  }
  
  static int ssl_io_hook_write(SSL *ssl, unsigned char *buf, int len)
  {
      int rc;
  
      if (ssl == NULL) {
          return -1;
      }
  
      rc = SSL_write(ssl, buf, len);
  
      if (rc < 0) {
          int ssl_err = SSL_get_error(ssl, rc);
  
          if (ssl_err == SSL_ERROR_WANT_WRITE) {
              /*
               * Simulate an EINTR in case OpenSSL wants to write more.
               */
              errno = EINTR;
          }
          else if (ssl_err == SSL_ERROR_SSL) {
              /*
               * Log SSL errors
               */
              conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
              ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                      "SSL error on writing data");
              ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
          }
          /*
           * XXX - Just trying to reflect the behaviour in 
           * openssl_state_machine.c [mod_tls]. TBD
           */
          rc = 0;
      }
      return rc;
  }
  
  static apr_status_t ssl_filter_write(ap_filter_t *f,
                                       const char *data,
                                       apr_size_t len)
  {
      SSLFilterRec *ctx = f->ctx;
      apr_size_t n;
  
      /* write SSL */
      n = ssl_io_hook_write(ctx->pssl, (unsigned char *)data, len);
  
      if (n != len) {
          conn_rec *c = f->c;
          char *reason = "reason unknown";
  
          /* XXX: probably a better way to determine this */
          if (SSL_total_renegotiations(ctx->pssl)) {
              reason = "likely due to failed renegotiation";
          }
  
          ap_log_error(APLOG_MARK, APLOG_ERR, 0, c->base_server,
                  "failed to write %d of %d bytes (%s)",
                  n > 0 ? len - n : len, len, reason);
  
          return APR_EINVAL;
      }
  
      return APR_SUCCESS;
  }
  
  static apr_status_t ssl_io_filter_Output(ap_filter_t *f,
                                           apr_bucket_brigade *bb)
  {
      apr_status_t status = APR_SUCCESS;
      SSLFilterRec *ctx = f->ctx;
  
      if (!ctx->pssl) {
          /* ssl_abort() has been called */
          return ap_pass_brigade(f->next, bb);
      }
  
      if ((status = ssl_hook_process_connection(ctx)) != APR_SUCCESS) {
          return status;
      }
  
      while (!APR_BRIGADE_EMPTY(bb)) {
          apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
  
          /* If it is a flush or EOS, we need to pass this down. 
           * These types do not require translation by OpenSSL.  
           */
          if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
              if ((status = BIO_bucket_flush(ctx->pbioWrite)) != APR_SUCCESS) {
                  return status;
              }
  
              if (APR_BUCKET_IS_EOS(bucket)) {
                  /* By definition, nothing can come after EOS.
                   * which also means we can pass the rest of this brigade
                   * without creating a new one since it only contains the
                   * EOS bucket.
                   */
  
                  if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
                      return status;
                  }
                  break;
              }
              else {
                  /* BIO_bucket_flush() already passed down a flush bucket
                   * if there was any data to be flushed.
                   */
                  apr_bucket_delete(bucket);
              }
          }
          else {
              /* read filter */
              const char *data;
              apr_size_t len;
  
              apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
              status = ssl_filter_write(f, data, len);
              apr_bucket_delete(bucket);
  
              if (status != APR_SUCCESS) {
                  break;
              }
          }
      }
  
      return status;
  }
  
  /*
   * ctx->cbuf is leftover plaintext from ssl_io_input_getline,
   * use what we have there first if any,
   * then go for more by calling ssl_io_hook_read.
   */
  static apr_status_t ssl_io_input_read(ssl_io_input_ctx_t *ctx,
                                        char *buf,
                                        apr_size_t *len)
  {
      apr_size_t wanted = *len;
      apr_size_t bytes = 0;
      int rc;
  
      *len = 0;
  
      if ((bytes = char_buffer_read(&ctx->cbuf, buf, wanted))) {
          *len = bytes;
          if (ctx->inbio.mode == AP_MODE_SPECULATIVE) {
              /* We want to rollback this read. */
              ctx->cbuf.value -= bytes;
              ctx->cbuf.length += bytes;
              return APR_SUCCESS;
          } 
          if ((*len >= wanted) || ctx->inbio.mode == AP_MODE_GETLINE) {
              return APR_SUCCESS;
          }
      }
  
      rc = ssl_io_hook_read(ctx->frec->pssl, buf + bytes, wanted - bytes);
  
      if (rc > 0) {
          *len += rc;
          if (ctx->inbio.mode == AP_MODE_SPECULATIVE) {
              char_buffer_write(&ctx->cbuf, buf, rc);
          }
      }
      else if ((rc == 0) && (errno != EINTR)) {
          /* something other than SSL_ERROR_WANT_READ */
          return APR_EOF;
      }
      else if ((rc == -1) && (ctx->inbio.rc == APR_SUCCESS)) {
          /*
           * bucket read from socket was successful,
           * but there was an error within the ssl runtime.
           */
          return APR_EGENERAL;
      }
  
      return ctx->inbio.rc;
  }
  
  static apr_status_t ssl_io_input_getline(ssl_io_input_ctx_t *ctx,
                                           char *buf,
                                           apr_size_t *len)
  {
      const char *pos = NULL;
      apr_status_t status;
      apr_size_t tmplen = *len, buflen = *len, offset = 0;
  
      *len = 0;
  
      /*
       * in most cases we get all the headers on the first SSL_read.
       * however, in certain cases SSL_read will only get a partial
       * chunk of the headers, so we try to read until LF is seen.
       */
  
      while (tmplen > 0) {
          status = ssl_io_input_read(ctx, buf + offset, &tmplen);
          
          if (status != APR_SUCCESS) {
              return status;
          }
  
          *len += tmplen;
  
          if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
              break;
          }
  
          offset += tmplen;
          tmplen = buflen - offset;
      }
  
      if (pos) {
          char *value;
          int length;
          apr_size_t bytes = pos - buf;
  
          bytes += 1;
          value = buf + bytes;
          length = *len - bytes;
  
          char_buffer_write(&ctx->cbuf, value, length);
  
          *len = bytes;
      }
  
      return APR_SUCCESS;
  }
  
  /* Just use a simple request.  Any request will work for this, because
   * we use a flag in the conn_rec->conn_vector now.  The fake request just
   * gets the request back to the Apache core so that a response can be sent.
   *
   * We should probably use a 0.9 request, but the BIO bucket code is calling
   * socket_bucket_read one extra time with all 0.9 requests from the client.
   * Until that is resolved, continue to use a 1.0 request, just like we
   * always have.
   */
  #define HTTP_ON_HTTPS_PORT \
      "GET / HTTP/1.0"
  
  #define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
      apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
                                 sizeof(HTTP_ON_HTTPS_PORT) - 1, \
                                 alloc)
  
  static void ssl_io_filter_disable(ap_filter_t *f)
  {
      ssl_io_input_ctx_t *ctx = f->ctx;
      ctx->inbio.ssl = NULL;
      ctx->frec->pssl = NULL;
  }
  
  static apr_status_t ssl_io_filter_error(ap_filter_t *f,
                                          apr_bucket_brigade *bb,
                                          apr_status_t status)
  {
      SSLConnRec *sslconn = myConnConfig(f->c);
      apr_bucket *bucket;
  
      switch (status) {
        case HTTP_BAD_REQUEST:
              /* log the situation */
              ap_log_error(APLOG_MARK, APLOG_ERR, 0,
                           f->c->base_server,
                           "SSL handshake failed: HTTP spoken on HTTPS port; "
                           "trying to send HTML error page");
              ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, f->c->base_server);
  
              sslconn->non_ssl_request = 1;
              ssl_io_filter_disable(f);
  
              /* fake the request line */
              bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
              break;
  
        default:
          return status;
      }
  
      APR_BRIGADE_INSERT_TAIL(bb, bucket);
  
      return APR_SUCCESS;
  }
  
  static apr_status_t ssl_io_filter_Input(ap_filter_t *f,
                                          apr_bucket_brigade *bb,
                                          ap_input_mode_t mode,
                                          apr_read_type_e block,
                                          apr_off_t readbytes)
  {
      apr_status_t status;
      ssl_io_input_ctx_t *ctx = f->ctx;
  
      apr_size_t len = sizeof(ctx->buffer);
      int is_init = (mode == AP_MODE_INIT);
  
      if (!ctx->inbio.ssl) {
          return ap_get_brigade(f->next, bb, mode, block, readbytes);
      }
  
      /* XXX: we don't currently support anything other than these modes. */
      if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE && 
          mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
          return APR_ENOTIMPL;
      }
  
      ctx->inbio.mode = mode;
      ctx->inbio.block = block;
  
      /* XXX: we could actually move ssl_hook_process_connection 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.
       */
      status = ssl_hook_process_connection(ctx->frec);
  
      if (status != APR_SUCCESS) {
          return ssl_io_filter_error(f, bb, status);
      }
  
      if (is_init) {
          /* protocol module needs to handshake before sending
           * data to client (e.g. NNTP or FTP)
           */
          return APR_SUCCESS;
      }
  
      if (ctx->inbio.mode == AP_MODE_READBYTES || 
          ctx->inbio.mode == AP_MODE_SPECULATIVE) {
          /* Protected from truncation, readbytes < MAX_SIZE_T 
           * FIXME: No, it's *not* protected.  -- jre */
          if (readbytes < len) {
              len = (apr_size_t)readbytes;
          }
          status = ssl_io_input_read(ctx, ctx->buffer, &len);
      }
      else if (ctx->inbio.mode == AP_MODE_GETLINE) {
          status = ssl_io_input_getline(ctx, ctx->buffer, &len);
      }
      else {
          /* We have no idea what you are talking about, so return an error. */
          return APR_ENOTIMPL;
      }
  
      if (status != APR_SUCCESS) {
          return ssl_io_filter_error(f, bb, status);
      }
  
      if (len > 0) {
          apr_bucket *bucket =
              apr_bucket_transient_create(ctx->buffer, len, f->c->bucket_alloc);
          APR_BRIGADE_INSERT_TAIL(bb, bucket);
      }
  
      return APR_SUCCESS;
  }
  
  static void ssl_io_input_add_filter(SSLFilterRec *frec, conn_rec *c,
                                      SSL *ssl)
  {
      ssl_io_input_ctx_t *ctx;
  
      ctx = apr_palloc(c->pool, sizeof(*ctx));
  
      frec->pInputFilter = ap_add_input_filter(ssl_io_filter, ctx, NULL, c);
  
      frec->pbioRead = BIO_new(BIO_s_in_bucket());
      frec->pbioRead->ptr = (void *)&ctx->inbio;
  
      ctx->frec = frec;
      ctx->inbio.ssl = ssl;
      ctx->inbio.wbio = frec->pbioWrite;
      ctx->inbio.f = frec->pInputFilter;
      ctx->inbio.bb = apr_brigade_create(c->pool, c->bucket_alloc);
      ctx->inbio.bucket = NULL;
      ctx->inbio.cbuf.length = 0;
  
      ctx->cbuf.length = 0;
  
      ctx->pool = c->pool;
  }
  
  static apr_status_t ssl_io_filter_cleanup (void *data)
  {
      apr_status_t ret;
      SSLFilterRec *pRec = (SSLFilterRec *)data;
  
      if (!pRec->pssl) {
          /* already been shutdown */
          return APR_SUCCESS;
      }
  
      if ((ret = ssl_hook_CloseConnection(pRec)) != APR_SUCCESS) {
          ap_log_error(APLOG_MARK, APLOG_ERR, ret, NULL,
                       "Error in ssl_hook_CloseConnection");
      }
  
      return ret;
  }
  
  void ssl_io_filter_init(conn_rec *c, SSL *ssl)
  {
      SSLFilterRec *filter;
  
      filter = apr_palloc(c->pool, sizeof(SSLFilterRec));
  
      filter->pOutputFilter   = ap_add_output_filter(ssl_io_filter,
                                                     filter, NULL, c);
  
      filter->pbioWrite       = BIO_new(BIO_s_bucket());
      filter->pbioWrite->ptr  = (void *)BIO_bucket_new(filter, c);
  
      ssl_io_input_add_filter(filter, c, ssl);
  
      SSL_set_bio(ssl, filter->pbioRead, filter->pbioWrite);
      filter->pssl            = ssl;
  
      apr_pool_cleanup_register(c->pool, (void*)filter,
                                ssl_io_filter_cleanup, apr_pool_cleanup_null);
  
      if (c->base_server->loglevel >= APLOG_DEBUG) {
          BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb);
          BIO_set_callback_arg(SSL_get_rbio(ssl), (void *)ssl);
      }
  
      return;
  }
  
  void ssl_io_filter_register(apr_pool_t *p)
  {
      ap_register_input_filter  (ssl_io_filter, ssl_io_filter_Input,  NULL, AP_FTYPE_CONNECTION + 5);
      ap_register_output_filter (ssl_io_filter, ssl_io_filter_Output, NULL, AP_FTYPE_CONNECTION + 5);
      return;
  }
  
  /*  _________________________________________________________________
  **
  **  I/O Data Debugging
  **  _________________________________________________________________
  */
  
  #define DUMP_WIDTH 16
  
  static void ssl_io_data_dump(server_rec *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++;
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
              "+-------------------------------------------------------------------------+");
      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));
          ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
                       "%s", buf);
      }
      if (trunc > 0)
          ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
                  "| %04ld - <SPACES/NULS>", len + trunc);
      ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
              "+-------------------------------------------------------------------------+");
      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;
      server_rec *s;
  
      if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL)
          return rc;
      if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL)
          return rc;
      s = c->base_server;
  
      if (   cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
          || cmd == (BIO_CB_READ |BIO_CB_RETURN) ) {
          if (rc >= 0) {
              ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                      "%s: %s %ld/%d bytes %s BIO#%p [mem: %p] %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(s, argp, rc);
          }
          else {
              ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                      "%s: I/O error, %d bytes expected to %s on BIO#%p [mem: %p]",
                      SSL_LIBRARY_NAME, argi,
                      (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
                      bio, argp);
          }
      }
      return rc;
  }
  