/* ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you 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.
 * ====================================================================
 *
 * Copyright 2014 Lieven Govaerts
 *
 * 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.
 */

#define APR_WANT_MEMFUNC
#include <apr_want.h>
#include <apr_strings.h>
#include <apr_uri.h>
#include <apr_lib.h>

#include <stdlib.h>

#ifndef OPENSSL_NO_OCSP /* requeires openssl 0.9.7 or later */
#include <openssl/ocsp.h>
#endif

#include "MockHTTP_private.h"

/* Copied from serf.  */
#if defined(APR_VERSION_AT_LEAST) && defined(WIN32)
#if APR_VERSION_AT_LEAST(1,4,0)
#define BROKEN_WSAPOLL
#endif
#endif

/* Forward declarations */
static apr_status_t initSSLCtx(_mhClientCtx_t *cctx);
static apr_status_t sslHandshake(_mhClientCtx_t *cctx);
static apr_status_t sslSocketWrite(_mhClientCtx_t *cctx, const char *data,
                                   apr_size_t *len);
static apr_status_t sslSocketRead(apr_socket_t *skt, void *baton, char *data,
                                  apr_size_t *len);
static apr_status_t sslSocketShutdown(_mhClientCtx_t *cctx, apr_shutdown_how_e how);

static apr_status_t renegotiateSSLSession(_mhClientCtx_t *cctx);

typedef apr_status_t (*handshake_func_t)(_mhClientCtx_t *cctx);
typedef apr_status_t (*reset_conn_func_t)(_mhClientCtx_t *cctx);
typedef apr_status_t (*send_func_t)(_mhClientCtx_t *cctx, const char *data,
                                    apr_size_t *len);
typedef apr_status_t (*receive_func_t)(apr_socket_t *skt, void *baton,
                                       char *data, apr_size_t *len);

typedef apr_status_t (*shutdown_func_t)(_mhClientCtx_t *cctx,
                                        apr_shutdown_how_e how);


typedef struct sslCtx_t sslCtx_t;
typedef struct bucket_t bucket_t;
static const apr_port_t DefaultSrvPort =   30080;
static const apr_port_t DefaultProxyPort = 38080;
static const apr_port_t DefaultOCSPResponderPort = 39080;

/* Buffer size for incoming and outgoing data */
#define BUFSIZE 32768

struct _mhClientCtx_t {
    apr_pool_t *pool;
    mhServCtx_t *serv_ctx;
    apr_socket_t *skt;         /* Socket for conn client <-> proxy/server */
    apr_int16_t reqevents;
    apr_socket_t *proxyskt;    /* Socket for conn proxy <-> server */
    apr_int16_t proxyreqevents;
    const char *proxyhost;     /* Proxy host:port */

    const char *respBody;
    apr_size_t respRem;
    apr_array_header_t *respQueue;  /* test will queue a response */
    mhResponse_t *currResp;         /* response in progress */
    unsigned int reqsReceived;      /* # of reqs received on this connection */
    mhRequest_t *req;
    bool closeConn;
    sslCtx_t *ssl_ctx;
    int protocols;                  /* SSL protocol versions */
    servMode_t mode;      /* default = server, but can switch to proxy/tunnel */

    send_func_t send;
    receive_func_t read;
    shutdown_func_t shutdown;

    bucket_t *stream; /* Bucket for incoming data */

    /* Proxy buffers */
    char osbuf[BUFSIZE]; /* buffer for data to be sent from proxy to the server */
    apr_size_t osbuflen;
    apr_size_t osbufrem;

    char ocbuf[BUFSIZE]; /* buffer for data to be sent from proxy/server to client */
    apr_size_t ocbuflen;
    apr_size_t ocbufrem;


    /* SSL-only callback functions, should be NULL when not implemented */
    handshake_func_t handshake;
    reset_conn_func_t reset;
    const char *keyFile;
    const char *passphrase;
    apr_array_header_t *certFiles;
    mhClientCertVerification_t clientCert;
    bool ocspEnabled;
};

/**
 * Start up a server in a separate thread.
 */
static void * APR_THREAD_FUNC run_thread(apr_thread_t *tid, void *baton)
{
    mhServCtx_t *ctx = baton;

    while (!ctx->cancelThread) {
        _mhRunServerLoop(ctx);
    }

    apr_thread_exit(tid, APR_SUCCESS);
    return NULL;
}

/**
 * Callback called when the mhServCtx_t pool is destroyed.
 */
static apr_status_t cleanupServer(void *baton)
{
    mhServCtx_t *ctx = baton;
    apr_status_t status = APR_SUCCESS;

    /* If mhCleanup() wasn't called before pool cleanup, the server is still
       running. Stop it here to avoid a crash, but this will result in a
       (currently unidentified) pool cleanup crash.
       Conclusion: Always run mhCleanup() at the end of a test!
     */
    mhStopServer(ctx);

    if (ctx->pollset) {
        apr_pollset_destroy(ctx->pollset);
        ctx->pollset = NULL;
    }
    /* We used to explicitly close the socket here, but APR already sets
       up a cleanup handler for that. As our cleanup is registered before
       that of the apr socket, ours will last, which the triggers a close(-1)
       from here on the unix implementations, as the initial close sets
       the socket to -1.

       On Windows APR checks before closing again */
    ctx->skt = NULL;

    return status;
}

/**
 * Callback, writes DATA of length LEN to the socket stored in CCTX.
 */
static apr_status_t socketWrite(_mhClientCtx_t *cctx, const char *data,
                                apr_size_t *len)
{
    return apr_socket_send(cctx->skt, data, len);
}

/**
 * Callback, reads data from the socket stored in CCTX and stores it in DATA,
 * the available bytes will be stored in *LEN.
 */
static apr_status_t socketRead(apr_socket_t *skt, void *baton, char *data,
                               apr_size_t *len)
{
    return apr_socket_recv(skt, data, len);
}

static apr_status_t socketShutdown(_mhClientCtx_t *cctx,
                                   apr_shutdown_how_e how)
{
    return apr_socket_shutdown(cctx->skt, how);
}

/**
 * Sets up a listener on the socket stored in CTX.
 */
static apr_status_t setupTCPServer(mhServCtx_t *ctx)
{
    apr_sockaddr_t *serv_addr;
    apr_pool_t *pool = ctx->pool;
    apr_status_t status;

    ctx->port = ctx->default_port;

    while (1) {
        STATUSERR(apr_sockaddr_info_get(&serv_addr, ctx->hostname,
                                        APR_UNSPEC, ctx->port, 0,
                                        pool));

        /* Create server socket */
        /* Note: this call requires APR v1.0.0 or higher */
        STATUSERR(apr_socket_create(&ctx->skt, serv_addr->family,
                                    SOCK_STREAM, 0, pool));

        STATUSERR(apr_socket_opt_set(ctx->skt, APR_SO_NONBLOCK, 1));
        STATUSERR(apr_socket_timeout_set(ctx->skt, 0));
        /* We used to call apr_socket_opt_set(ctx->skt, APR_SO_REUSEADDR, 1),
           but that is severly broken when we run multiple tests in parallel,
           as that may just listen on a port where another process is
           listening too.

           See http://stackoverflow.com/a/14388707/2094

           Instead of trying to work around the limitations per platform
           by using specific flags, the best fix is to just *not* use
           this option and use a different port if necessary.
         */

        /* Try the next port until bind succeeds */
        status = apr_socket_bind(ctx->skt, serv_addr);
        if (status != APR_SUCCESS) {
            apr_socket_close(ctx->skt);
            ctx->skt = NULL;
            ctx->port++;
            continue;
        }
        /* Listen for clients */
        STATUSERR(apr_socket_listen(ctx->skt, SOMAXCONN));
        break;
    };

    /* Create a new pollset, avoid broken WSAPoll implemenation on Windows. */
#ifdef BROKEN_WSAPOLL
    STATUSERR(apr_pollset_create_ex(&ctx->pollset, 32, pool, 0,
                                    APR_POLLSET_SELECT));
#else
    STATUSERR(apr_pollset_create(&ctx->pollset, 32, pool, 0));
#endif

    /* Listen for POLLIN events on this socket */
    {
        apr_pollfd_t pfd = { 0 };

        pfd.desc_type = APR_POLL_SOCKET;
        pfd.desc.s = ctx->skt;
        pfd.reqevents = APR_POLLIN | APR_POLLHUP | APR_POLLERR;

        STATUSERR(apr_pollset_add(ctx->pollset, &pfd));
    }

    return APR_SUCCESS;
}

/**
 * Opens a non-blocking connection to a remote server at URL (in form
 * localhost:30080).
 */
static apr_status_t connectToServer(mhServCtx_t *ctx, _mhClientCtx_t *cctx)
{
    apr_sockaddr_t *address;
    char *host, *scopeid;
    apr_port_t port;
    apr_status_t status;

    STATUSERR(apr_parse_addr_port(&host, &scopeid, &port, cctx->proxyhost,
                                  cctx->pool));
    STATUSERR(apr_sockaddr_info_get(&address, host, APR_UNSPEC,
                                    port, 0, cctx->pool));

    /* Create server socket */
    /* Note: this call requires APR v1.0.0 or higher */
    STATUSERR(apr_socket_create(&cctx->proxyskt, address->family,
                                SOCK_STREAM, 0, cctx->pool));
    STATUSERR(apr_socket_opt_set(cctx->proxyskt, APR_SO_NONBLOCK, 1));
    STATUSERR(apr_socket_timeout_set(cctx->proxyskt, 0));

    status = apr_socket_connect(cctx->proxyskt, address);
    if (status == APR_SUCCESS || APR_STATUS_IS_EINPROGRESS(status)) {
        apr_pollfd_t pfd = { 0 };

        pfd.desc_type = APR_POLL_SOCKET;
        pfd.desc.s = cctx->proxyskt;
        pfd.reqevents = APR_POLLIN | APR_POLLOUT;
        pfd.client_data = cctx;
        STATUSERR(apr_pollset_add(ctx->pollset, &pfd));
        cctx->proxyreqevents = pfd.reqevents;

        /* ### If EINPROGRESS some operations may fail until the
               socket is really connected. Shouldn't we wait? */
    }

    return status;
}

/**
 * Initialize the server context.
 */
static mhServCtx_t *
initServCtx(const MockHTTP *mh, const char *hostname, apr_port_t port)
{
    apr_pool_t *pool = mh->pool;

    mhServCtx_t *ctx = apr_pcalloc(pool, sizeof(mhServCtx_t));
    ctx->pool = pool;
    ctx->mh = mh;
    ctx->hostname = apr_pstrdup(pool, hostname);
    ctx->default_port = port;
    ctx->port = 0;
    ctx->clients = apr_array_make(pool, 5, sizeof(_mhClientCtx_t *));
    ctx->reqsReceived = apr_array_make(pool, 5, sizeof(mhRequest_t *));
    /* Default settings */
    ctx->clientCert = mhCCVerifyNone;
    ctx->protocols = mhProtoUnspecified;
    ctx->threading = mhThreadMain;
    ctx->reqMatchers = apr_array_make(pool, 5, sizeof(ReqMatcherRespPair_t *));
    ctx->incompleteReqMatchers = apr_array_make(pool, 5,
                                                sizeof(ReqMatcherRespPair_t *));

    apr_pool_cleanup_register(pool, ctx,
                              cleanupServer,
                              apr_pool_cleanup_null);

    return ctx;
}

/******************************************************************************/
/* Parse a request structure from incoming data                               */
/******************************************************************************/

/**
 * Initialize a mhRequest_t object
 */
mhRequest_t *_mhInitRequest(apr_pool_t *pool, requestType_t type)
{
    mhRequest_t *req = apr_pcalloc(pool, sizeof(mhRequest_t));
    req->pool = pool;
    req->type = type;
    req->hdrs = apr_table_make(pool, 5);
    req->hdrHashes = apr_array_make(pool, 5, sizeof(unsigned long));
    req->body = apr_array_make(pool, 5, sizeof(struct iovec));
    req->chunks = apr_array_make(pool, 5, sizeof(struct iovec));

    return req;
}

/******************* BUCKETS **************************************************/
typedef struct _mhBucketType_t {

    const char *name;

    apr_status_t (*read)(bucket_t *bucket, apr_size_t requested,
                         const char **data, apr_size_t *len);

    apr_status_t (*readLine)(bucket_t *bucket,
                             /* int acceptable, int *found, CR, LF? */
                             const char **data, apr_size_t *len);

    apr_status_t (*peek)(bucket_t *bucket, apr_size_t *len);

} _mhBucketType_t;

struct bucket_t {

    /** the type of this bucket */
    const _mhBucketType_t *type;

    /** bucket-private data */
    void *data;
};

/* Buffered Socket buffer */
typedef struct _bufferedSocketCtx_t {
    apr_socket_t *skt;
    receive_func_t read;
    void *read_baton;
    char buf[BUFSIZE];  /* buffer for data received from the client @ server */
    apr_size_t remaining;  /* remaining bytes in the buffer */
    apr_size_t offset;  /* offset of the first available byte */
} _bufferedSocketCtx_t;

/**
 * Reads data from the socket and stores it in CCTX->buf.
 */
static apr_status_t readFromSocket(bucket_t *bkt)
{
    _bufferedSocketCtx_t *ctx = bkt->data;
    apr_status_t status;

    // assert(ctx->offset = 0);
    apr_size_t len = BUFSIZE - ctx->remaining;
    /* If our buffer is full, process more data first */
    if (len == 0)
        return APR_EAGAIN;

    STATUSREADERR(ctx->read(ctx->skt, ctx->read_baton,
                            ctx->buf + ctx->remaining, &len));
    if (len) {
        _mhLog(MH_VERBOSE, ctx->skt,
               "recvd with status %d:\n%.*s\n---- %d ----\n",
               status, (unsigned int)len, ctx->buf + ctx->remaining,
               (unsigned int)len);
        ctx->remaining += len;
    }
    return status;
}

/**
 * Read a complete line from the buffer in CCTX.
 * *LEN will be non-0 if a line ending with CRLF was found. BUF will be copied
 * in mem allocatod from cctx->pool, cctx->buf ptrs will be moved.
 */
static apr_status_t
buffSktReadLine(bucket_t *bkt, const char **data, apr_size_t *len)
{
    _bufferedSocketCtx_t *ctx = bkt->data;
    const char *ptr = ctx->buf;
    apr_status_t status;

    /* eat data read in a previous read call */
    if (ctx->offset > 0) {
        memmove(ctx->buf, ctx->buf + ctx->offset, BUFSIZE - ctx->offset);
        ctx->offset = 0;
    }

    status = readFromSocket(bkt);

    /* return one line of data */
    *len = 0;
    while (*ptr && ptr - ctx->buf < ctx->remaining) {
        if (*ptr == '\r' && *(ptr+1) == '\n') {
            *len = ptr - ctx->buf + 2;
            *data = ctx->buf;

            ctx->offset += *len;
            ctx->remaining -= *len;

            break;
        }
        ptr++;
    }

    if (ctx->remaining > 0)
        return APR_SUCCESS;

    return status;
}

static apr_status_t buffSktRead(bucket_t *bkt, apr_size_t requested,
                                const char **data, apr_size_t *len)
{
    _bufferedSocketCtx_t *ctx = bkt->data;
    apr_status_t status;

    /* eat data read in a previous read call */
    if (ctx->offset > 0) {
        memmove(ctx->buf, ctx->buf + ctx->offset, BUFSIZE - ctx->offset);
        ctx->offset = 0;
    }

    status = readFromSocket(bkt);

    /* return requested data */
    *len = ctx->remaining <= requested ? ctx->remaining : requested; /* this packet */
    *data = ctx->buf;

    ctx->offset += *len;
    ctx->remaining -= *len;

    if (ctx->remaining > 0)
        return APR_SUCCESS;

    return status;
}


static apr_status_t buffSktPeek(bucket_t *bkt, apr_size_t *len)
{
    _bufferedSocketCtx_t *ctx = bkt->data;
    apr_status_t status;

    /* eat data read in a previous read call */
    if (ctx->offset > 0) {
        memmove(ctx->buf, ctx->buf + ctx->offset, BUFSIZE - ctx->offset);
        ctx->offset = 0;
    }

    status = readFromSocket(bkt);

    if (status && !APR_STATUS_IS_EOF(status) && !APR_STATUS_IS_EAGAIN(status)
        && ctx->remaining > 0) {

        /* If there was a socket read error, assume that it will be returned
           on the next call to readFromSocket */
        status = APR_SUCCESS;
    }

    *len = ctx->remaining;

    return status;
}

const _mhBucketType_t BufferedSocketBucketType = {
    "BUFFSOCKET",
    buffSktRead,
    buffSktReadLine,
    buffSktPeek,
};

static bucket_t *
createBufferedSocketBucket(apr_socket_t *skt, receive_func_t read,
                           void *baton, apr_pool_t *pool)
{
    _bufferedSocketCtx_t *ctx = apr_pcalloc(pool, sizeof(_bufferedSocketCtx_t));
    bucket_t *bkt = apr_palloc(pool, sizeof(bucket_t));

    ctx->skt = skt;
    ctx->read = read;
    ctx->read_baton = baton;
    bkt->type = &BufferedSocketBucketType;
    bkt->data = ctx;
    return bkt;
}

#define FAIL_ON_EOL(ptr)\
    if (*ptr == '\0') return APR_EGENERAL; /* TODO: error code */

/**
 * Reads the request line from the buffer in CCTX, REQ will be updated
 * with the info read from the request line.
 *
 * Returns APR_EAGAIN if the request line isn't completely available,
 *         APR_SUCCESS + *DONE = YES if request line parsed.
 *         error in case the request line couldn't be parsed successfully
 */
static apr_status_t
readReqLine(bucket_t *bkt, mhRequest_t *req, bool *done)
{
    const char *start, *ptr, *version;
    const char *buf;
    apr_size_t len;
    apr_status_t status;

    *done = FALSE;

    status = bkt->type->readLine(bkt, &buf, &len);
    if (!len) return status;

    /* TODO: add checks for incomplete request line */
    start = ptr = buf;
    while (*ptr && *ptr != ' ' && *ptr != '\r') ptr++;
    FAIL_ON_EOL(ptr);
    req->method = apr_pstrndup(req->pool, start, ptr-start);
    req->methodCode = methodToCode(req->method);

    ptr++; start = ptr;
    while (*ptr && *ptr != ' ' && *ptr != '\r') ptr++;
    FAIL_ON_EOL(ptr);
    req->url = apr_pstrndup(req->pool, start, ptr-start);

    ptr++; start = ptr;
    while (*ptr && *ptr != ' ' && *ptr != '\r') ptr++;
    if (ptr - start != strlen("HTTP/x.y")) {
        return APR_EGENERAL;
    }
    version = apr_pstrndup(req->pool, start, ptr-start);
    req->version = (version[5] - '0') * 10 +
    version[7] - '0';

    *done = TRUE;

    return APR_SUCCESS;
}

/* simple hash implementation (djb2). */
unsigned long calculateHeaderHash(const char *hdr, const char *val)
{
    unsigned long hash = 5381;
    int c;

    if (!val)
        return 0;

    while ((c = apr_tolower(*hdr++)))
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
    hash = ((hash << 5) + hash) + ':';
    while ((c = *val++))
        hash = ((hash << 5) + hash) + c;

    return hash;
}

static void setRequestHeader(mhRequest_t *req, const char *hdr, const char *val)
{
    apr_table_t *hdrs = req->hdrs;
    unsigned long hash;

    apr_table_add(hdrs, hdr, val);
    hash = calculateHeaderHash(hdr, val);
    APR_ARRAY_PUSH(req->hdrHashes, unsigned long) = hash;
}

/**
 * Reads a HTTP header from the buffer in CCTX, header will be added to REQ.
 *
 * Returns APR_EAGAIN if a header line isn't completely available.
 *         APR_SUCCESS if a header line was successfully parsed, maybe more
 *                     are available.
 *           + *DONE = YES when the last header was successfully parsed.
 *         error in case the header line couldn't be parsed successfully
 */
static apr_status_t
readHeader(bucket_t *bkt, mhRequest_t *req, bool *done)
{
    const char *buf;
    apr_size_t len;
    apr_status_t status;

    *done = NO;

    STATUSREADERR(bkt->type->readLine(bkt, &buf, &len));
    if (!len) return APR_EAGAIN;

    /* Last header? */
    if (len == 2 && *buf == '\r' && *(buf+1) == '\n') {
        *done = YES;
        return APR_SUCCESS;
    } if (len < 5) {
        /* TODO: error handling. header line is too short */
        return APR_EGENERAL;
    } else {
        const char *start = buf, *ptr = buf, *end = buf + len;
        const char *hdr, *val;

        /* Read header from a line in the form of 'Header: value' */
        while (*ptr && ptr < end && *ptr != ':' && *ptr != '\r') ptr++;
        hdr = apr_pstrndup(req->pool, start, ptr-start);

        /* skip : and blanks */
        ptr++; while (*ptr && ptr < end && *ptr == ' ') ptr++; start = ptr;

        /* Read value */
        while (*ptr && ptr < end && *ptr != '\r') ptr++;
        val = apr_pstrndup(req->pool, start, ptr-start);

        setRequestHeader(req, hdr, val);
    }
    return APR_SUCCESS;
}

/**
 * Append a block of data in BUF of length LEN (not-'\0' terminated) to the
 * list in REQ. Data will be copied in the REQ->pool.
 */
static void
storeRawDataBlock(mhRequest_t *req, const char *buf, apr_size_t len)
{
    struct iovec vec;
    vec.iov_base = apr_pstrndup(req->pool, buf, len);
    vec.iov_len = len;
    *((struct iovec *)apr_array_push(req->body)) = vec;
    req->bodyLen += len;
}

/**
 * Reads the unencoded (not chunked) body from the buffer in CCTX. The length
 * of the body is determined by reading the "Content-Length" header in REQ.
 * The body will be copied in REQ->pool and stored in REQ.
 *
 * Returns APR_EAGAIN if the body isn't completely available.
 *         APR_SUCCESS + *DONE = YES when the whole body was read completely.
 *         error in case the "Content-Length" header isn't set.
 */
static apr_status_t readBody(bucket_t *bkt, mhRequest_t *req, bool *done)
{
    const char *clstr, *data;
    char *body;
    apr_size_t cl;
    apr_size_t len;
    apr_status_t status;

    req->chunked = NO;

    clstr = getHeader(req->hdrs, "Content-Length");
    /* TODO: error if no Content-Length header */
    cl = atol(clstr);

    len = cl - req->bodyLen; /* remaining # of bytes */

    STATUSREADERR(bkt->type->read(bkt, len, &data, &len));

    /* copy data to the request */
    if (req->body == NULL) {
        req->body = apr_palloc(req->pool, sizeof(struct iovec) * 256);
    }
    body = apr_palloc(req->pool, len + 1);
    memcpy(body, data, len);
    *(body + len) = '\0';
    storeRawDataBlock(req, body, len);

    if (req->bodyLen < cl)
        return APR_EAGAIN;

    *done = YES;
    return APR_SUCCESS;
}

/**
 * Reads a chunk of the body from the buffer in CCTX. The length
 * of the body is determined by reading the chunk header, length of current
 * chunk and partial read data will be stored in REQ->chunks.
 * The chunk will be copied in REQ->pool and stored in REQ.
 *
 * Returns APR_EAGAIN if the chunk isn't completely available.
 *         APR_SUCCESS if a chunk was read completely, maybe more are available.
 *           + *DONE = YES when the last chunk and the trailer were read.
 *         error in case of problems parsing the chunk header, length or trailer.
 */
static apr_status_t readChunk(bucket_t *bkt, mhRequest_t *req, bool *done)
{
    apr_status_t status;

    *done = NO;

    switch (req->readState) {
        case ReadStateBody:
        case ReadStateChunked:
            req->readState = ReadStateChunkedHeader;
            /* fall through */
        case ReadStateChunkedHeader:
        {
            struct iovec vec;
            const char *data;
            apr_size_t len, chlen;

            STATUSREADERR(bkt->type->readLine(bkt, &data, &len));
            if (!len)
                return APR_EAGAIN;
            storeRawDataBlock(req, data, len);

            /* read hex chunked length */
            chlen = (apr_size_t)apr_strtoi64(data, NULL, 16);
            vec.iov_len = chlen;
            APR_ARRAY_PUSH(req->chunks, struct iovec) = vec;
            if (chlen == 0) {
                req->readState = ReadStateChunkedTrailer;
                return APR_SUCCESS;
            }

            req->readState = ReadStateChunkedChunk;
            /* fall through */
        }
        case ReadStateChunkedChunk:
        {
            const char *data;
            struct iovec *vec;
            apr_size_t chlen, curchunklen, len;

            vec = &APR_ARRAY_IDX(req->chunks, req->chunks->nelts - 1,
                                 struct iovec);
            chlen = vec->iov_len;

            /* already read some data of this chunk? */
            if (req->incomplete_chunk) {
                const char *tmp;
                curchunklen = strlen(vec->iov_base);
                /* read as much as possible from remaining part */
                STATUSREADERR(bkt->type->read(bkt, chlen - curchunklen,
                                              &data, &len));
                tmp = apr_pstrndup(req->pool, data, len);
                storeRawDataBlock(req, data, len);
                vec->iov_base = apr_pstrcat(req->pool, vec->iov_base, tmp, NULL);
                curchunklen += len;
            } else {
                /* read as much as possible */
                STATUSREADERR(bkt->type->read(bkt, chlen, &data, &len));
                vec->iov_base = apr_pstrndup(req->pool, data, len);
                storeRawDataBlock(req, data, len);
                curchunklen = len;
            }

            if (curchunklen < chlen) {
                /* More data is needed to read one chunk */
                req->incomplete_chunk = YES;
                return APR_EAGAIN;
            }
            req->incomplete_chunk = NO;
            req->readState = ReadStateChunkedTrailer;

            /* fall through */
        }
        case ReadStateChunkedTrailer:
        {
            const char *data;
            apr_size_t len, chlen;
            struct iovec vec;

            vec = APR_ARRAY_IDX(req->chunks, req->chunks->nelts - 1, struct iovec);
            chlen = vec.iov_len;

            STATUSREADERR(bkt->type->readLine(bkt, &data, &len));
            if (len < 2)
                return APR_EAGAIN;
            storeRawDataBlock(req, data, len);
            if (len == 2 && *data == '\r' && *(data+1) == '\n') {
                if (chlen == 0) {
                    /* body ends with chunk of length 0 */
                    *done = YES;
                    req->readState = ReadStateDone;
                    /* remove the 0-chunk from the request*/
                    apr_array_pop(req->chunks);
                } else {
                    req->readState = ReadStateChunked;
                }
            } else {
                return APR_EGENERAL; /* TODO: error code */
            }
            break;
        }
        default:
            break;
    }

    return APR_SUCCESS;
}

/**
 * Keeps reading chunks until no more data is available.
 *
 * Returns APR_EAGAIN if a chunk isn't completely available.
 *         APR_SUCCESS + *DONE = YES when the last chunk and the trailer were
 *              read.
 *         error in case of problems parsing the chunk header, length or trailer.
 */
static apr_status_t
readChunked(bucket_t *bkt, mhRequest_t *req, bool *done)
{
    apr_status_t status = APR_SUCCESS;

    *done = NO;
    req->chunked = YES;

    while (*done == NO)
        STATUSERR(readChunk(bkt, req, done));

    return status;
}

/**
 * New request data is available, read status line/hdrs/body (chunks).
 *
 * Returns APR_EAGAIN: wait for more data
 *         APR_EOF: request received, or no more data available.
 *         MH_STATUS_INCOMPLETE_REQUEST: APR_EOF but the request wasn't received
 *             completely.
 */
static apr_status_t readRequest(_mhClientCtx_t *cctx, mhRequest_t **preq)
{
    mhRequest_t *req = *preq;
    bucket_t *bkt;
    apr_status_t status = APR_SUCCESS;

    bkt = cctx->stream;
    if (req == NULL) {
        apr_size_t len;
        STATUSREADERR(bkt->type->peek(bkt, &len));
        if (!len)
            return status;
        req = *preq = _mhInitRequest(cctx->pool, RequestTypeHTTP);
    }

    while (!status) { /* read all available data */
        bool done = NO;

        switch(cctx->req->readState) {
            case ReadStateStatusLine: /* status line */
                STATUSREADERR(readReqLine(bkt, req, &done));
                if (done) req->readState = ReadStateHeaders;
                break;
            case ReadStateHeaders: /* headers */
                STATUSREADERR(readHeader(bkt, req, &done));
                if (done) req->readState = ReadStateBody;
                break;
            case ReadStateBody: /* body */
            case ReadStateChunked:
            case ReadStateChunkedHeader:
            case ReadStateChunkedChunk:
            case ReadStateChunkedTrailer:
            {
                const char *clstr, *chstr;
                chstr = getHeader(req->hdrs, "Transfer-Encoding");
                /* TODO: chunked can be one of more encodings */
                /* Read Transfer-Encoding first, ignore C-L when T-E is set */
                if (chstr && apr_strnatcasecmp(chstr, "chunked") == 0) {
                    STATUSREADERR(readChunked(bkt, req, &done));
                } else {
                    clstr = getHeader(req->hdrs, "Content-Length");
                    if (clstr) {
                        STATUSREADERR(readBody(bkt, req, &done));
                    } else {
                        done = YES; /* no body to read */
                    }
                }
                if (done) {
                    _mhLog(MH_VERBOSE, cctx->skt, "Server received request: %s %s\n",
                           req->method, req->url);
                    return APR_EOF;
                }
            }
            case ReadStateDone:
                break;
        }
    }
#if 0
    /* TODO: fix or cleanup */
    if (!cctx->buflen) {
        if (APR_STATUS_IS_EOF(status))
            return MH_STATUS_INCOMPLETE_REQUEST;

        return status;
    }
#endif

    return status;
}

/******************************************************************************/
/* Send a response                                                            */
/******************************************************************************/

/**
 * Translate a HTTP status code to a string representation.
 */
static const char *codeToString(unsigned int code)
{
    switch(code) {
        case 100: return "Continue"; break;
        case 101: return "Switching Protocols"; break;
        case 200: return "OK"; break;
        case 201: return "Created"; break;
        case 202: return "Accepted"; break;
        case 203: return "Non-Authoritative Information"; break;
        case 204: return "No Content"; break;
        case 205: return "Reset Content"; break;
        case 206: return "Partial Content"; break;
        case 300: return "Multiple Choices"; break;
        case 301: return "Moved Permanently"; break;
        case 302: return "Found"; break;
        case 303: return "See Other"; break;
        case 304: return "Not Modified"; break;
        case 305: return "Use Proxy"; break;
        case 307: return "Temporary Redirect"; break;
        case 400: return "Bad Request"; break;
        case 401: return "Unauthorized"; break;
        case 402: return "Payment Required"; break;
        case 403: return "Forbidden"; break;
        case 404: return "Not Found"; break;
        case 405: return "Method Not Allowed"; break;
        case 406: return "Not Acceptable"; break;
        case 407: return "Proxy Authentication Required"; break;
        case 408: return "Request Timeout"; break;
        case 409: return "Conflict"; break;
        case 410: return "Gone"; break;
        case 411: return "Length Required"; break;
        case 412: return "Precondition Failed"; break;
        case 413: return "Request Entity Too Large"; break;
        case 414: return "Request-URI Too Long"; break;
        case 415: return "Unsupported Media Type"; break;
        case 416: return "Requested Range Not Satisfiable"; break;
        case 417: return "Expectation Failed"; break;
        case 500: return "Internal Server Error"; break;
        case 501: return "Not Implemented"; break;
        case 502: return "Bad Gateway"; break;
        case 503: return "Service Unavailable"; break;
        case 504: return "Gateway Timeout"; break;
        case 505: return "HTTP Version Not Supported"; break;
        default: return "<not defined>";
    }
}

/**
 * Serializes the response RESP to a '\0'-terminated string, allocated in POOL.
 */
static char *respToString(apr_pool_t *pool, mhResponse_t *resp)
{
    char *str;
    const apr_table_entry_t *elts;
    const apr_array_header_t *arr;
    int i;

    /* status line */
    str = apr_psprintf(pool, "HTTP/1.1 %d %s\r\n", resp->code,
                       codeToString(resp->code));

    /* headers */
    arr = apr_table_elts(resp->hdrs);
    elts = (const apr_table_entry_t *)arr->elts;

    for (i = 0; i < arr->nelts; ++i) {
        str = apr_psprintf(pool, "%s%s: %s\r\n", str, elts[i].key, elts[i].val);
    }
    str = apr_psprintf(pool, "%s\r\n", str);

    /* body */
    if (resp->chunked == NO) {
        for (i = 0 ; i < resp->body->nelts; i++) {
            struct iovec vec;

            vec = APR_ARRAY_IDX(resp->body, i, struct iovec);
            str = apr_psprintf(pool, "%s%.*s", str, (unsigned int)vec.iov_len,
                               (const char *)vec.iov_base);
        }
    } else {
        bool emptyChunk = NO; /* empty response should atleast have 0-chunk */
        for (i = 0 ; i < resp->chunks->nelts; i++) {
            struct iovec vec;

            vec = APR_ARRAY_IDX(resp->chunks, i, struct iovec);
            str = apr_psprintf(pool, "%s%" APR_UINT64_T_HEX_FMT "\r\n%.*s\r\n",
                               str, (apr_uint64_t)vec.iov_len,
                               (unsigned int)vec.iov_len, (char *)vec.iov_base);
            emptyChunk = vec.iov_len == 0 ? YES : NO;
        }
        if (!emptyChunk) /* Add 0 chunk only if last chunk wasn't empty already */
            str = apr_psprintf(pool, "%s0\r\n\r\n", str);
    }
    return str;
}

/**
 * Serializes the response RESP and writes it to the socket. Unwritten data will
 * be stored in CCTX->respBody.
 */
static apr_status_t writeResponse(_mhClientCtx_t *cctx, mhResponse_t *resp)
{
    apr_pool_t *pool = cctx->pool;
    apr_size_t len;
    apr_status_t status;

    if (!cctx->respRem) {
        _mhBuildResponse(resp);
        if (resp->raw_data) {
            cctx->respBody = resp->raw_data;
            cctx->respRem = resp->raw_data_length;
        } else {
            cctx->respBody = respToString(pool, resp);
            cctx->respRem = strlen(cctx->respBody);
        }
    }

    len = cctx->respRem;
    STATUSREADERR(cctx->send(cctx, cctx->respBody, &len));
    _mhLog(MH_VERBOSE, cctx->skt, "sent with status %d:\n%.*s\n---- %d ----\n",
           status, (unsigned int)len, cctx->respBody, (unsigned int)len);

    if (len < cctx->respRem) {
        cctx->respBody += len;
        cctx->respRem -= len;
        cctx->currResp = resp;
    } else {
        cctx->respBody = NULL;
        cctx->respRem = 0;
        cctx->currResp = 0;
        return APR_EOF;
    }
    return status;
}

/******************************************************************************/
/* Match a request                                                            */
/******************************************************************************/

/**
 * Stores a request matcher RM on the list of matchers for server CTX.
 */
void mhPushRequest(MockHTTP *mh, mhServCtx_t *ctx, mhRequestMatcher_t *rm)
{
    apr_array_header_t *matchers;
    ReqMatcherRespPair_t *pair;
    int i;

    if (ctx) {
        pair = apr_palloc(ctx->pool, sizeof(ReqMatcherRespPair_t));
    } else {
        pair = apr_palloc(mh->pool, sizeof(ReqMatcherRespPair_t));
    }
    pair->rm = rm;
    pair->resp = NULL;
    pair->action = mhActionInitiateNone;

    /* Check if any of this request's matchers work on an incomplete request. */
    for (i = 0 ; i < rm->matchers->nelts; i++) {
        const mhReqMatcherBldr_t *mp;

        mp = APR_ARRAY_IDX(rm->matchers, i, mhReqMatcherBldr_t *);
        if (mp->match_incomplete == YES) {
            rm->incomplete = YES;
            break;
        }
    }

    switch (rm->type) {
        case RequestTypeHTTP:
            if (ctx) {
                matchers = rm->incomplete ? ctx->incompleteReqMatchers :
                                            ctx->reqMatchers;
            } else {
                matchers = rm->incomplete ? mh->incompleteReqMatchers :
                                            mh->reqMatchers;
            }
            break;
        case RequestTypeOCSP:
            matchers = mh->ocspReqMatchers;
            break;
        default:
            /* Unsupported request type */
            return;
    }
    *((ReqMatcherRespPair_t **)apr_array_push(matchers)) = pair;
}

/**
 * Tries to match the request REQ with any of the request matchers MATCHERS.
 * Returns NO if the request wasn't matched.
 *         YES + *RESP + *ACTION if the request was matched successfully.
 */

/* TOOD:
   This function is the main bottleneck for performance. Possible fixes:
   - if a test is setup to continuously add new request matchers while sending
     requests, evaluation the matchers from last to first drastically
     improves matching performance. Problem: user expects matches are run in
     order of definition.
   - when using a header with an unique value per request, we could create
     an index on that header:value (hashtable or b-tree) to bypass this whole
     loop. Probably requires a new API so the user can specify the index.
   - create a universal string that contains the union of all criteria
     of all matchers, e.g. "GET/header1:value1/header2:value2" and create an
     index on that string. Problem is updating that index with new matchers when
     they use new criteria (OTOH: as long as the criteria are sorted this
     shouldn't be a problem, a criteria not defined = 0 bytes in the string).
 */
static bool
matchRequest(mhRequest_t *req, mhResponse_t **resp,
             mhAction_t *action, const apr_array_header_t *matchers)
{
    int i;

    for (i = 0 ; i < matchers->nelts; i++) {
        const ReqMatcherRespPair_t *pair;

        pair = APR_ARRAY_IDX(matchers, i, ReqMatcherRespPair_t *);

        if (_mhRequestMatcherMatch(pair->rm, req) == YES) {
            *resp = pair->resp;
            *action = pair->action;
            return YES;
        }
    }

    *resp = NULL;
    return NO;
}

/**
 * Tries to match a complete request REQ with the list of complete request
 * matchers.
 * Returns NO if the request wasn't matched.
 *         YES + *RESP + *ACTION if the request was matched successfully.
 */
static bool
_mhMatchRequest(const mhServCtx_t *ctx, const _mhClientCtx_t *cctx,
                mhRequest_t *req, mhResponse_t **resp, mhAction_t *action)
{
    bool found;
    const MockHTTP *mh = ctx->mh;

    /* Try to see if a request matcher for this server exists */
    found = matchRequest(req, resp, action, ctx->reqMatchers);
    if (found) return found;

    /* Nope, then see of there's a request matcher for all servers */
    found = matchRequest(req, resp, action, mh->reqMatchers);
    if (found) return found;

    _mhLog(MH_VERBOSE, cctx->skt, "Couldn't match request!\n");
    return NO;
}

/**
 * Tries to match an incomplete (partial) request REQ with the list of
 * incomplete request matchers.
 * Returns NO if the request wasn't matched.
 *         YES + *RESP + *ACTION if the request was matched successfully.
 */
static bool
_mhMatchIncompleteRequest(const mhServCtx_t *ctx, const _mhClientCtx_t *cctx,
                          mhRequest_t *req, mhResponse_t **resp,
                          mhAction_t *action)
{
    bool found;
    const MockHTTP *mh = ctx->mh;

    /* Try to see if a request matcher for this server exists */
    found = matchRequest(req, resp, action, ctx->incompleteReqMatchers);
    if (found) return found;

    /* Nope, then see of there's a request matcher for all servers */
    found = matchRequest(req, resp, action, mh->incompleteReqMatchers);
    if (found) return found;

    _mhLog(MH_VERBOSE, cctx->skt, "Couldn't match incomplete request!\n");
    return NO;
}

/**
 * Deep copy of a response RESP to a new response allocated in POOL.
 */
static mhResponse_t *cloneResponse(apr_pool_t *pool, mhResponse_t *resp)
{
    mhResponse_t *clone;
    clone = apr_pmemdup(pool, resp, sizeof(mhResponse_t));
    clone->hdrs = apr_table_clone(pool, resp->hdrs);
    if (resp->chunks)
        clone->chunks = apr_array_copy(pool, resp->chunks);
    if (resp->body)
        clone->body = apr_array_copy(pool, resp->body);
    return clone;
}


/******************************************************************************/
/* Process socket events                                                      */
/******************************************************************************/

/**
 * Process events on connection proxy <-> server, reads all incoming data,
 * writes all outgoing data.
 * This only supports SSL TUNNEL mode at this time.
 */
static apr_status_t processProxy(_mhClientCtx_t *cctx, const apr_pollfd_t *desc)
{
    apr_status_t status = APR_SUCCESS;

    if ((desc->rtnevents & APR_POLLOUT) && (cctx->osbuflen > 0)) {
        apr_size_t len = cctx->osbuflen;
        STATUSREADERR(apr_socket_send(cctx->proxyskt, cctx->osbuf, &len));
        _mhLog(MH_VERBOSE, cctx->proxyskt,
               "Proxy sent to server, status %d:\n%.*s\n---- %d ----\n",
               status, (unsigned int)len, cctx->osbuf, (unsigned int)len);
        cctx->osbufrem += len;
        cctx->osbuflen -= len;
    }

    if (desc->rtnevents & APR_POLLIN) {
        char *buf = cctx->ocbuf + cctx->ocbuflen;
        apr_size_t len = cctx->ocbufrem;
        STATUSREADERR(apr_socket_recv(cctx->proxyskt, buf, &len));
        _mhLog(MH_VERBOSE, cctx->proxyskt,
               "Proxy received from server, status %d:\n%.*s\n---- %d ----\n",
               status, (unsigned int)len, buf, (unsigned int)len);
        cctx->ocbuflen += len;
        cctx->ocbufrem -= len;
    }

    return status;

}

/**
 * Process events on connection client <-> proxy or client <-> server
 * Reads all incoming data, tries to match complete and/or incomplete requests,
 * and then writes responses back to the socket CCTX.
 *
 * Returns APR_EOF when the connection should be closed.
 **/
static apr_status_t processServer(mhServCtx_t *ctx, _mhClientCtx_t *cctx,
                                  const apr_pollfd_t *desc)
{
    apr_status_t status = APR_EAGAIN;
    bucket_t *stream = cctx->stream;
    apr_size_t len;

    /* First sent any pending responses before reading the next request. */
    if (desc->rtnevents & APR_POLLOUT &&
        (cctx->currResp || cctx->respQueue->nelts || cctx->ocbuflen)) {
        mhResponse_t *resp;

        if (cctx->ocbuflen) {
            len = cctx->ocbuflen;
            STATUSREADERR(apr_socket_send(cctx->skt, cctx->ocbuf, &len));
            _mhLog(MH_VERBOSE, cctx->skt,
                   "Proxy/Server sent to client, status %d:\n%.*s\n---- %d ----\n",
                   status, (unsigned int)len, cctx->ocbuf, (unsigned int)len);
            cctx->ocbufrem += len;
            cctx->ocbuflen -= len;
            /* TODO: len < ocbuflen? */
            return status; /* can't send more data */
        }

        while (cctx->currResp || cctx->respQueue->nelts > 0) {
            resp = cctx->currResp ? cctx->currResp :
                            *(mhResponse_t **)apr_array_pop(cctx->respQueue);
            if (resp) {
                _mhLog(MH_VERBOSE, cctx->skt, "Sending response to client.\n");

                status = writeResponse(cctx, resp);
                if (status == APR_EOF) {
                    cctx->currResp = NULL;
                    ctx->mh->verifyStats->requestsResponded++;
                    if (resp->closeConn) {
                        _mhLog(MH_VERBOSE, cctx->skt,
                               "Actively closing connection.\n");
                        cctx->shutdown(cctx, APR_SHUTDOWN_READWRITE);
                        return APR_EOF;
                    }
                    status = APR_SUCCESS;
                } else {
                    cctx->currResp = resp;
                    status = APR_EAGAIN;
                    break;
                }
            } else {
                return APR_EGENERAL;
            }
        }
    }

    status = stream->type->peek(stream, &len);
    if (desc->rtnevents & APR_POLLIN || len > 0) {
        mhAction_t action;

        switch (cctx->mode) {
          case ModeServer:
          case ModeProxy:
            /* Read partial or full requests */
            STATUSREADERR(readRequest(cctx, &cctx->req));

            if (!cctx->req)
                return status;

            if (status == APR_EOF) {
                mhResponse_t *resp;

                /* complete request received */
                ctx->mh->verifyStats->requestsReceived++;
                cctx->reqsReceived++;
                ctx->reqState = FullReqReceived;
                *((mhRequest_t **)apr_array_push(ctx->reqsReceived)) = cctx->req;
                if (_mhMatchRequest(ctx, cctx, cctx->req,
                                    &resp, &action) == YES) {
                    ctx->mh->verifyStats->requestsMatched++;
                    if (resp) {
                        _mhLog(MH_VERBOSE, cctx->skt,
                               "Request matched, queueing response.\n");
                        resp = cloneResponse(cctx->pool, resp);
                    } else {
                        _mhLog(MH_VERBOSE, cctx->skt,
                               "Request matched, queueing default response.\n");
                        resp = cloneResponse(cctx->pool, ctx->mh->defResponse);
                    }

                    switch (action) {
                      case mhActionInitiateSSLTunnel:
                        _mhLog(MH_VERBOSE, cctx->skt, "Initiating SSL tunnel.\n");
                        cctx->mode = ModeTunnel;
                        cctx->proxyhost = apr_pstrdup(cctx->pool,
                                                      cctx->req->url);
                        connectToServer(ctx, cctx);
                        break;
                      case mhActionSSLRenegotiate:
                        _mhLog(MH_VERBOSE, cctx->skt, "Renegotiating SSL "
                               "session.\n");
                        STATUSREADERR(renegotiateSSLSession(cctx));
                        break;
                      case mhActionCloseConnection:
                        /* close conn after response */
                        resp->closeConn = YES;
                        cctx->shutdown(cctx, APR_SHUTDOWN_READ);
                        break;
                      default:
                        break;
                    }
                } else {
                    ctx->mh->verifyStats->requestsNotMatched++;
                    _mhLog(MH_VERBOSE, cctx->skt,
                           "Request found no match, queueing error response.\n");
                    resp = cloneResponse(cctx->pool, ctx->mh->defErrorResponse);
                }
                if (ctx->maxRequests && cctx->reqsReceived >= ctx->maxRequests) {
                    setHeader(resp->hdrs, "Connection", "close");
                    resp->closeConn = YES;
                    cctx->shutdown(cctx, APR_SHUTDOWN_READ);
                }

                /* Link the request to the response, and push the response on the
                   queue back to the client */
                resp->req = cctx->req;
                *((mhResponse_t **)apr_array_push(cctx->respQueue)) = resp;
                cctx->req = NULL;

                return APR_SUCCESS;
            } else if (status == APR_SUCCESS || APR_STATUS_IS_EAGAIN(status)) {
                ctx->reqState = PartialReqReceived;
            }

            if (ctx->incompleteReqMatchers->nelts > 0 ||
                ctx->mh->incompleteReqMatchers->nelts > 0) {
                mhResponse_t *resp = NULL;
                /* (currently) incomplete request received? */
                if (_mhMatchIncompleteRequest(ctx, cctx, cctx->req,
                                              &resp, &action) == YES) {
                    _mhLog(MH_VERBOSE, cctx->skt,
                           "Incomplete request matched, queueing response.\n");
                    ctx->mh->verifyStats->requestsMatched++;
                    if (!resp)
                        resp = cloneResponse(cctx->pool, ctx->mh->defResponse);
                    resp->req = cctx->req;
                    *((mhResponse_t **)apr_array_push(cctx->respQueue)) = resp;
                    cctx->req = NULL;
                    return APR_SUCCESS;
                }
            }
            break;
          case ModeTunnel:
            {
                const char *data;
                /* Forward raw data */
                len = cctx->osbufrem;
                STATUSREADERR(stream->type->read(stream, len, &data, &len));

                if (len) {
                    memmove(cctx->osbuf, data, len);
                    _mhLog(MH_VERBOSE, cctx->skt,
                           "recvd with status %d:\n%.*s\n---- %d ----\n",
                           status, (unsigned int)len, cctx->osbuf + cctx->osbuflen,
                           (unsigned int)len);
                    cctx->osbuflen += len;
                    cctx->osbufrem -= len;
                }
            }
            break;
          default:
            break;
        }
    }

    if ((desc->rtnevents & (APR_POLLHUP | APR_POLLERR))
        || APR_STATUS_IS_ECONNRESET(status)
        || APR_STATUS_IS_ECONNABORTED(status)) {

        /* overwrites the result of stream->type->peek() */
        status = APR_EOF;
    }

    return status;
}

/**
 * Initialize the client context. This stores all info related to one client
 * socket in the server.
 */
static apr_status_t initClientCtx(_mhClientCtx_t **ppctx,
                                  apr_pool_t *pool, mhServCtx_t *serv_ctx,
                                  apr_socket_t *cskt, mhServerType_t type)
{
    _mhClientCtx_t *cctx;
    apr_pool_t *ccpool;
    apr_pool_create(&ccpool, pool);

    cctx = apr_pcalloc(ccpool, sizeof(_mhClientCtx_t));
    cctx->pool = ccpool;
    cctx->serv_ctx = serv_ctx;
    cctx->skt = cskt;
    cctx->ocbuflen = 0;
    cctx->ocbufrem = BUFSIZE;
    cctx->osbuflen = 0;
    cctx->osbufrem = BUFSIZE;
    cctx->closeConn = NO;
    cctx->respQueue = apr_array_make(pool, 5, sizeof(mhResponse_t *));
    cctx->currResp = NULL;
    cctx->mode = ModeServer;
    if (type == mhHTTPv1Server || type == mhHTTPv11Server ||
        type == mhHTTPv1Proxy || type == mhHTTPv11Proxy) {
        cctx->read = socketRead;
        cctx->send = socketWrite;
        cctx->shutdown = socketShutdown;
    }
#ifdef MOCKHTTP_OPENSSL
    if (type == mhHTTPSv1Server || type == mhHTTPSv11Server) {
        apr_status_t status;

        cctx->handshake = sslHandshake;
        cctx->read = sslSocketRead;
        cctx->send = sslSocketWrite;
        cctx->shutdown = sslSocketShutdown;
        cctx->keyFile = serv_ctx->keyFile;
        cctx->passphrase = serv_ctx->passphrase;
        cctx->certFiles = serv_ctx->certFiles;
        cctx->clientCert = serv_ctx->clientCert;
        cctx->protocols = serv_ctx->protocols;
        cctx->ocspEnabled = serv_ctx->ocspEnabled;

        status = initSSLCtx(cctx);

        if (status)
            return status;
    }
#endif
    cctx->stream = createBufferedSocketBucket(cskt, cctx->read,
                                              cctx->ssl_ctx, ccpool);

    *ppctx = cctx;

    return APR_SUCCESS;
}

static void closeAndRemoveClientCtx(mhServCtx_t *ctx, _mhClientCtx_t *cctx)
{
    int i;
    apr_array_header_t *clients = ctx->clients;
    apr_pollfd_t pfd = { 0 };

    /* Close socket and clean up client context */
    pfd.desc_type = APR_POLL_SOCKET;
    pfd.desc.s = cctx->skt;
    pfd.reqevents = cctx->reqevents;
    apr_pollset_remove(ctx->pollset, &pfd);
    apr_socket_close(cctx->skt);
    cctx->skt = NULL;

    if (cctx->proxyskt) {
        pfd.desc_type = APR_POLL_SOCKET;
        pfd.desc.s = cctx->proxyskt;
        pfd.reqevents = cctx->proxyreqevents;
        apr_pollset_remove(ctx->pollset, &pfd);
        apr_socket_close(cctx->proxyskt);
        cctx->proxyskt = NULL;
    }

    /* TODO: a linked list would be more efficient. */
    /* Swap the last element to the location of the element to be
     deleted, then decrease the size of the array by 1 */
    for (i = 0; i < clients->nelts; i++) {
        _mhClientCtx_t *tmp = APR_ARRAY_IDX(clients, i, _mhClientCtx_t *);
        if (cctx == tmp) {
            if (i+1 < clients->nelts) {
                _mhClientCtx_t *last;
                last = APR_ARRAY_IDX(clients, clients->nelts - 1,
                                     _mhClientCtx_t *);
                *(_mhClientCtx_t **)&clients->elts[i] = last;
            }
            clients->nelts--;
        }
    }
}

/**
 * Process all events on all sockets related to this server CTX, i.e. the server
 * socket for incoming connections, the client socket(s) and the outgoing
 * socket in case this server acts as a proxy.
 */
apr_status_t _mhRunServerLoop(mhServCtx_t *ctx)
{
    apr_int32_t num;
    const apr_pollfd_t *desc;
    apr_status_t status;

    if (!ctx->pollset) {
        /* Somebody ignored the result of mhStartServer().
           ... Not that hard given that it returns void */
        return APR_EINCOMPLETE;
    }

    if (ctx->reqState == FullReqReceived)
        ctx->reqState = NoReqsReceived;
#if 0
    /* TODO: add a dirty flag to every socket wrapping context, only listen
       for writeable events with the socket is dirty */
    apr_pollfd_t pfd = { 0 };
    cctx = ctx->cctx;
    /* something to write */
    if (cctx && cctx->skt) {
        pfd.desc_type = APR_POLL_SOCKET;
        pfd.desc.s = cctx->skt;
        pfd.reqevents = cctx->reqevents;
        pfd.client_data = cctx;
        apr_pollset_remove(ctx->pollset, &pfd);

        cctx->reqevents = APR_POLLIN;
        if (cctx->currResp || cctx->respQueue->nelts > 0 || cctx->obuflen > 0)
            cctx->reqevents |= APR_POLLOUT;
        pfd.reqevents = ctx->cctx->reqevents;
        STATUSERR(apr_pollset_add(ctx->pollset, &pfd));
    }
#endif
    STATUSERR(apr_pollset_poll(ctx->pollset, APR_USEC_PER_SEC / 100,
                               &num, &desc));

    /* The same socket can be returned multiple times by apr_pollset_poll() */
    while (num--) {
        if (desc->desc.s == ctx->skt) {
            _mhClientCtx_t *cctx;
            apr_socket_t *cskt;
            apr_pollfd_t pfd = { 0 };

            _mhLog(MH_VERBOSE, ctx->skt, "Accepting client connection.\n");

            STATUSERR(apr_socket_accept(&cskt, ctx->skt, ctx->pool));

            STATUSERR(apr_socket_opt_set(cskt, APR_SO_NONBLOCK, 1));
            STATUSERR(apr_socket_timeout_set(cskt, 0));

            /* Push a client context on the ctx->clients stack */
            STATUSERR(initClientCtx(&cctx, ctx->pool, ctx, cskt, ctx->type));
            pfd.desc_type = APR_POLL_SOCKET;
            pfd.desc.s = cskt;
            pfd.reqevents = APR_POLLIN | APR_POLLOUT | APR_POLLHUP | APR_POLLERR;
            pfd.client_data = cctx;
            STATUSERR(apr_pollset_add(ctx->pollset, &pfd));
            cctx->reqevents = pfd.reqevents;
            *((_mhClientCtx_t **)apr_array_push(ctx->clients)) = cctx;

        } else {
            _mhClientCtx_t *cctx = desc->client_data;

            if (!cctx || !cctx->skt) {
                _mhLog(MH_VERBOSE, NULL, "Getting event from unknown socket.\n");
            } else if (desc->desc.s == cctx->proxyskt) {
                /* Connection proxy <-> server */
                STATUSREADERR(processProxy(cctx, desc));
                if (status == APR_EOF) {
                    apr_pollfd_t pfd = { 0 };
                    pfd.desc_type = APR_POLL_SOCKET;
                    pfd.desc.s = cctx->proxyskt;
                    pfd.reqevents = cctx->proxyreqevents;
                    apr_pollset_remove(ctx->pollset, &pfd);
                    apr_socket_close(cctx->proxyskt);
                    cctx->proxyskt = NULL;

                }
            } else {
                /* Connection client <-> proxy/server */
                status = APR_SUCCESS;

                if (cctx->handshake)
                    status = cctx->handshake(cctx);

                /* APR_SUCCESS -> handshake finished or not needed */
                if (status == APR_SUCCESS) {
                    STATUSREADERR(processServer(ctx, cctx, desc));
                    if (status == APR_EOF) {
                        /* Close the socket and an associated proxy skt */
                        closeAndRemoveClientCtx(ctx, cctx);
                        break;
                    }
                }
            }
        }
        desc++;
    }

    return status;
}

/******************************************************************************/
/* Init HTTP server                                                           */
/******************************************************************************/

/**
 * Creates a new server on localhost and on the default server port.
 */
mhServCtx_t *mhNewServer(MockHTTP *mh)
{
    mh->servCtx = initServCtx(mh, "localhost", DefaultSrvPort);
    mh->servCtx->type = mhGenericServer;
    return mh->servCtx;
}

/**
 * Creates a new proxy server on localhost and on the default proxy port.
 */
mhServCtx_t *mhNewProxy(MockHTTP *mh)
{
    mh->proxyCtx = initServCtx(mh, "localhost", DefaultProxyPort);
    mh->proxyCtx->type = mhGenericProxy;
    return mh->proxyCtx;
}

/**
 * Creates a new OCSP responder on localhost and on its default port.
 */
mhServCtx_t *mhNewOCSPResponder(MockHTTP *mh)
{
    mh->ocspRespCtx = initServCtx(mh, "localhost", DefaultOCSPResponderPort);
    mh->ocspRespCtx->type = mhOCSPResponder;
    return mh->ocspRespCtx;
}

/**
 * Returns the server context associated with id SERVERID.
 */
mhServCtx_t *mhFindServerByID(const MockHTTP *mh, const char *serverID)
{
    if (mh->servCtx && mh->servCtx->serverID &&
        strcmp(mh->servCtx->serverID, serverID) == 0) {
        return mh->servCtx;
    }

    if (mh->proxyCtx && mh->proxyCtx->serverID &&
        strcmp(mh->proxyCtx->serverID, serverID) == 0) {
        return mh->proxyCtx;
    }
    return NULL;
}

/**
 * Takes a list of builders of type mhServerSetupBldr_t *'s and executes them
 * one by one (in the order they are passed as arguments) to configure the
 * server SERV_CTX.
 */
void mhConfigServer(mhServCtx_t *serv_ctx, ...)
{
    va_list argp;

    /* Build the server configuration */
    va_start(argp, serv_ctx);
    while (1) {
        mhServerSetupBldr_t *ssb;
        ssb = va_arg(argp, mhServerSetupBldr_t *);
        if (ssb == NULL)
            break;
        if (ssb->builder.type == BuilderTypeNone)
            continue;
        if (ssb->builder.type != BuilderTypeServerSetup) {
            _mhErrorUnexpectedBuilder(serv_ctx->mh, ssb, BuilderTypeServerSetup);
            break;
        }
        ssb->serversetup(ssb, serv_ctx);
    }
    va_end(argp);

    if (serv_ctx->protocols == mhProtoUnspecified) {
        serv_ctx->protocols = mhProtoAllSecure;
    }
    if (!serv_ctx->serverID) {
        if (serv_ctx->type == mhGenericProxy) {
            serv_ctx->serverID = DEFAULT_PROXY_ID;
        } else {
            serv_ctx->serverID = DEFAULT_SERVER_ID;
        }
    }
}

/**
 * Starts the server CTX, makes it start listening for incoming connections.
 */
void mhStartServer(mhServCtx_t *ctx)
{
    apr_thread_t *thread;
    mhError_t err = MOCKHTTP_NO_ERROR;
    apr_status_t status;

    if (ctx->threading == mhThreadSeparate) {
#if APR_HAS_THREADS
        /* Setup a non-blocking TCP server */
        status = setupTCPServer(ctx);
        if (!status) {
            status = apr_thread_create(&thread, NULL, run_thread,
                                       ctx, ctx->pool);
            if (!status)
                ctx->threadid = thread;
        }
#else
        status = APR_EGENERAL;
#endif
    } else {
        /* Setup a non-blocking TCP server */
        status = setupTCPServer(ctx);
    }

    if (status) {
        err = MOCKHTTP_SETUP_FAILED;
    }
    /* TODO: store error message */
}

void mhStopServer(mhServCtx_t *ctx)
{
    apr_status_t status;
#ifdef APR_HAS_THREADS
    if (ctx->threading == mhThreadSeparate && ctx->threadid) {
        ctx->cancelThread = YES;
        apr_thread_join(&status, ctx->threadid);
    }
#endif
}


/**
 * Factory function, creates a builder of type mhServerSetupBldr_t.
 */
static mhServerSetupBldr_t *createServerSetupBldr(apr_pool_t *pool)
{
    mhServerSetupBldr_t *ssb = apr_pcalloc(pool, sizeof(mhServerSetupBldr_t));
    ssb->builder.magic = MagicKey;
    ssb->builder.type = BuilderTypeServerSetup;
    return ssb;
}

/**
 * Builder callback, sets the server id on server CTX.
 */
static bool set_server_id(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->serverID = ssb->baton;
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets the server id
 */
mhServerSetupBldr_t *mhSetServerID(mhServCtx_t *ctx, const char *serverID)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->baton = apr_pstrdup(pool, serverID);
    ssb->serversetup = set_server_id;
    return ssb;
}

/**
 * Builder callback, sets the number of maximum requests per connection on
 * server CTX.
 */
static bool
set_server_maxreqs_per_conn(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->maxRequests = ssb->ibaton;
    return YES;
}

/**
 * Create a builder of type mhServerSetupBldr_t, sets the number of maximum
 * requests per connection.
 */
mhServerSetupBldr_t *
mhSetServerMaxRequestsPerConn(mhServCtx_t *ctx, unsigned int maxRequests)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = maxRequests;
    ssb->serversetup = set_server_maxreqs_per_conn;
    return ssb;
}

apr_port_t mhServerByIDPortNr(const MockHTTP *mh, const char *serverID)
{
    mhServCtx_t *ctx = mhFindServerByID(mh, serverID);

    if (ctx) {
        if (!ctx->port)
            abort();
        return ctx->port;
    }
    return 0;
}

apr_port_t mhServerPortNr(const MockHTTP *mh)
{
    return mhServerByIDPortNr(mh, DEFAULT_SERVER_ID);
}

apr_port_t mhProxyPortNr(const MockHTTP *mh)
{
    return mhServerByIDPortNr(mh, DEFAULT_PROXY_ID);
}

/**
 * Builder callback, sets the port number on server CTX.
 */
static bool set_server_port(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->default_port = (apr_port_t)ssb->ibaton;
    return YES;
}

/**
 * Create a builder of type mhServerSetupBldr_t, sets the server port
 */
mhServerSetupBldr_t *mhSetServerPort(mhServCtx_t *ctx, unsigned int port)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = port;
    ssb->serversetup = set_server_port;
    return ssb;
}

static bool set_server_protocol(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
  ctx->alpn = ssb->baton;
  return YES;
}

mhServerSetupBldr_t *mhSetServerProtocol(mhServCtx_t *ctx, const char *protocols)
{
  apr_pool_t *pool = ctx->pool;
  mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
  ssb->baton = apr_pstrdup(pool, protocols);
  ssb->serversetup = set_server_protocol;
  return ssb;
}

/**
 * Builder callback, sets the server type on server CTX.
 */
static bool set_server_type(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    mhServerType_t type = ssb->ibaton;

    switch (ctx->type) {
        case mhGenericServer:
            if (type == mhHTTPv1)
                ctx->type = mhHTTPv1Server;
            else if (type == mhHTTPv11)
                ctx->type = mhHTTPv11Server;
            else if (type == mhHTTPSv1)
                ctx->type = mhHTTPSv1Server;
            else
                ctx->type = mhHTTPSv11Server;
            break;
        case mhGenericProxy:
            if (type == mhHTTPv1)
                ctx->type = mhHTTPv1Proxy;
            else if (type == mhHTTPv11)
                ctx->type = mhHTTPv11Proxy;
            else if (type == mhHTTPSv1)
                ctx->type = mhHTTPSv1Proxy;
            else
                ctx->type = mhHTTPSv11Proxy;
            break;
        default:
            /* TODO: error in test configuration. */
            break;
    }
    return YES;
}

/**
 * Create a builder of type mhServerSetupBldr_t, sets the server type
 */
mhServerSetupBldr_t *mhSetServerType(mhServCtx_t *ctx, mhServerType_t type)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = type;
    ssb->serversetup = set_server_type;
    return ssb;
}

/**
 * Builder callback, sets the server's threading mode on server CTX.
 */
static bool
set_server_threading(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->threading = ssb->ibaton;
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets the server threading mode.
 */
mhServerSetupBldr_t *
mhSetServerThreading(mhServCtx_t *ctx, mhThreading_t threading)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = threading;
    ssb->serversetup = set_server_threading;
    return ssb;
}

/**
 * Builder callback, sets the prefix for certificate paths on server CTX.
 */
static bool
set_server_cert_prefix(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->certFilesPrefix = ssb->baton;
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets the prefix for cert paths.
 */
mhServerSetupBldr_t *
mhSetServerCertPrefix(mhServCtx_t *ctx, const char *prefix)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->baton = apr_pstrdup(pool, prefix);
    ssb->serversetup = set_server_cert_prefix;
    return ssb;
}

/**
 * Builder callback, sets the path of the server private key file on server CTX.
 */
static bool
set_server_key_file(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    const char *keyFile = ssb->baton;
    if (ctx->certFilesPrefix) {
        ctx->keyFile = apr_pstrcat(ctx->pool, ctx->certFilesPrefix, "/",
                                   keyFile, NULL);
    } else {
        ctx->keyFile = keyFile;
    }
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets the private key file.
 */
mhServerSetupBldr_t *
mhSetServerCertKeyFile(mhServCtx_t *ctx, const char *keyFile)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->baton = apr_pstrdup(pool, keyFile);
    ssb->serversetup = set_server_key_file;
    return ssb;
}

/**
 * Builder callback, sets the passphrase to be used to decrypt the private key
 * file on server CTX.
 */
static bool
set_server_key_passphrase(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->passphrase = ssb->baton;
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets the private key passphrase.
 */
mhServerSetupBldr_t *
mhSetServerCertKeyPassPhrase(mhServCtx_t *ctx, const char *passphrase)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->baton = apr_pstrdup(pool, passphrase);
    ssb->serversetup = set_server_key_passphrase;
    return ssb;
}

/**
 * Builder callback, adds a list of certificate files on server CTX.
 */
static bool
add_server_cert_files(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    int i;
    const apr_array_header_t *certFiles = ssb->baton;

    if (!ctx->certFiles)
        ctx->certFiles = apr_array_make(ctx->pool, 5, sizeof(const char *));

    /* Copy over the cert file paths, add prefix */
    for (i = 0; i < certFiles->nelts; i++) {
        const char *certFile = APR_ARRAY_IDX(certFiles, i, const char *);

        if (ctx->certFilesPrefix)
            certFile = apr_pstrcat(ctx->pool, ctx->certFilesPrefix, "/",
                                   certFile, NULL);
        *((const char **)apr_array_push(ctx->certFiles)) = certFile;
    }
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, adds certificates.
 */
mhServerSetupBldr_t *
mhAddServerCertFiles(mhServCtx_t *ctx, ...)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    va_list argp;
    apr_array_header_t *certFiles;

    certFiles = apr_array_make(ctx->pool, 5, sizeof(const char *));

    va_start(argp, ctx);
    while (1) {
        const char *certFile = va_arg(argp, const char *);
        if (certFile == NULL)
            break;
        *((const char **)apr_array_push(certFiles)) = apr_pstrdup(pool, certFile);
    }
    va_end(argp);

    ssb->baton = certFiles;
    ssb->serversetup = add_server_cert_files;

    return ssb;
}


/**
 * Create a builder of type mhServerSetupBldr_t, adds an array of certificates.
 */
mhServerSetupBldr_t *
mhAddServerCertFileArray(mhServCtx_t *ctx, const char **certFiles)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    apr_array_header_t *certFileAry;
    const char *certFile;
    int i = 0;

    certFileAry = apr_array_make(ctx->pool, 5, sizeof(const char *));

    do {
        certFile = certFiles[i++];
        *((const char **)apr_array_push(certFileAry)) = apr_pstrdup(pool,
                                                                    certFile);
    } while (certFiles[i] != NULL);
    ssb->baton = certFileAry;
    ssb->serversetup = add_server_cert_files;
    return ssb;
}

/**
 * Builder callback, sets how server CTX should request client certificates.
 */
static bool
set_server_request_client_cert(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->clientCert = ssb->ibaton;
    return YES;
}


/**
 * Create a builder of type mhServerSetupBldr_t, sets how the server should
 * request client certificates.
 */
mhServerSetupBldr_t *
mhSetServerRequestClientCert(mhServCtx_t *ctx, mhClientCertVerification_t v)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = v;
    ssb->serversetup = set_server_request_client_cert;
    return ssb;
}

/**
 * Builder callback, adds an allowed SSL/TLS version on server CTX.
 */
static bool
add_server_ssl_protocol(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->protocols |= ssb->ibaton;
    return YES;
}

/**
 * Create a builder of type mhServerSetupBldr_t, adds allowed SSL/TLS version.
 */
mhServerSetupBldr_t *mhAddSSLProtocol(mhServCtx_t *ctx, mhSSLProtocol_t proto)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = proto;
    ssb->serversetup = add_server_ssl_protocol;
    return ssb;
}

/**
 * Builder callback, sets the "OCSP Enabled" flag.
 */
static bool
enable_server_ocsp(const mhServerSetupBldr_t *ssb, mhServCtx_t *ctx)
{
    ctx->ocspEnabled = ssb->ibaton;
    return YES;
}

/**
 * Create a builder of type mhServerSetupBldr_t, enable OCSP stapling support.
 */
mhServerSetupBldr_t *
mhSetServerEnableOCSP(mhServCtx_t *ctx)
{
    apr_pool_t *pool = ctx->pool;
    mhServerSetupBldr_t *ssb = createServerSetupBldr(pool);
    ssb->ibaton = YES;
    ssb->serversetup = enable_server_ocsp;
    return ssb;
}


#ifdef MOCKHTTP_OPENSSL
/******************************************************************************/
/* Init HTTPS server                                                          */
/******************************************************************************/
#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

struct sslCtx_t {
    bool handshake_done;
    bool renegotiate;
    apr_status_t bio_status;

    SSL_CTX* ctx;
    SSL* ssl;
    BIO *bio;
    BIO_METHOD *biom;

    char read_buffer[8192];
};

static int init_done = 0;

/**
 * OpenSSL callback, returns the passphrase used to decrypt the private key.
 */
static int pem_passwd_cb(char *buf, int size, int rwflag, void *userdata)
{
    _mhClientCtx_t *cctx = userdata;

    if (!cctx->passphrase)
        return 0;

    strncpy(buf, cctx->passphrase, size);
    buf[size - 1] = '\0';
    return strlen(buf);
}

/**
 * OpenSSL BIO callback. Creates a new BIO structure.
 */
static int bio_apr_socket_create(BIO *bio)
{
#ifndef SERF_NO_SSL_BIO_WRAPPERS
    BIO_set_shutdown(bio, 1);
    BIO_set_init(bio, 1);
    BIO_set_data(bio, NULL);
#else
    bio->shutdown = 1;
    bio->init = 1;
    bio->num = -1;
    bio->ptr = NULL;
#endif

    return 1;
}

static void bio_set_data(BIO *bio, void *data)
{
#ifndef SERF_NO_SSL_BIO_WRAPPERS
    BIO_set_data(bio, data);
#else
    bio->ptr = data;
#endif
}

static void *bio_get_data(BIO *bio)
{
#ifndef SERF_NO_SSL_BIO_WRAPPERS
    return BIO_get_data(bio);
#else
    return bio->ptr;
#endif
}

/**
 * OpenSSL BIO callback. Cleans up the BIO structure.
 */
static int bio_apr_socket_destroy(BIO *bio)
{
    /* Did we already free this? */
    if (bio == NULL) {
        return 0;
    }

    return 1;
}

/**
 * OpenSSL BIO callback.
 */
static long bio_apr_socket_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
    long ret = 1;

    switch (cmd) {
        default:
            /* abort(); */
            break;
        case BIO_CTRL_FLUSH:
            /* At this point we can't force a flush. */
            break;
        case BIO_CTRL_PUSH:
        case BIO_CTRL_POP:
            ret = 0;
            break;
    }
    return ret;
}

/**
 * OpenSSL BIO callback. Reads data from a socket, returns the amount read.
 */
static int bio_apr_socket_read(BIO *bio, char *in, int inlen)
{
    apr_size_t len = inlen;
    _mhClientCtx_t *cctx = bio_get_data(bio);
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    apr_status_t status;

    BIO_clear_retry_flags(bio);

    status = apr_socket_recv(cctx->skt, in, &len);
    ssl_ctx->bio_status = status;

    if (len > 0)
        _mhLog(MH_VERBOSE, cctx->skt, "Read %d bytes from ssl socket with "
               "status %d.\n", len, status);

    if (APR_STATUS_IS_EAGAIN(status)) {
        BIO_set_retry_read(bio);
    }

    if (READ_ERROR(status))
        return -1;

    return len ? len : -1;
}

/**
 * OpenSSL BIO callback. Write data to a socket, returns the amount written.
 */
static int bio_apr_socket_write(BIO *bio, const char *in, int inlen)
{
    apr_size_t len = inlen;
    _mhClientCtx_t *cctx = bio_get_data(bio);
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    apr_status_t status;

    BIO_clear_retry_flags(bio);

    status = apr_socket_send(cctx->skt, in, &len);
    ssl_ctx->bio_status = status;

    if (len > 0)
        _mhLog(MH_VERBOSE, cctx->skt, "Wrote %d of %d bytes to ssl socket with "
               "status %d.\n", len, inlen, status);

    if (APR_STATUS_IS_EAGAIN(status)) {
        BIO_set_retry_write(bio);
    }

    if (READ_ERROR(status))
        return -1;

    return len ? len : -1;
}


#ifdef SERF_NO_SSL_BIO_WRAPPERS
static BIO_METHOD bio_apr_socket_method = {
    BIO_TYPE_SOCKET,
    "APR sockets",
    bio_apr_socket_write,
    bio_apr_socket_read,
    NULL,                        /* Is this called? */
    NULL,                        /* Is this called? */
    bio_apr_socket_ctrl,
    bio_apr_socket_create,
    bio_apr_socket_destroy,
#ifdef OPENSSL_VERSION_NUMBER
    NULL /* sslc does not have the callback_ctrl field */
#endif
};
#endif

static BIO_METHOD *bio_meth_apr_socket_new(void)
{
    BIO_METHOD *biom = NULL;

#ifndef SERF_NO_SSL_BIO_WRAPPERS
    biom = BIO_meth_new(BIO_TYPE_SOCKET, "APR sockets");
    if (biom) {
        BIO_meth_set_write(biom, bio_apr_socket_write);
        BIO_meth_set_read(biom, bio_apr_socket_read);
        BIO_meth_set_ctrl(biom, bio_apr_socket_ctrl);
        BIO_meth_set_create(biom, bio_apr_socket_create);
        BIO_meth_set_destroy(biom, bio_apr_socket_destroy);
    }
#else
    biom = &bio_apr_socket_method;
#endif

    return biom;
}

static void bio_meth_free(BIO_METHOD *biom)
{
#ifndef SERF_NO_SSL_BIO_WRAPPERS
    BIO_meth_free(biom);
#endif
}

#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
static int ocspCreateResponse(OCSP_RESPONSE **resp, mhOCSPRespnseStatus_t status)
{
    int ret = 1;
    int ocspStatus;
/*
    OCSP_BASICRESP *basicResp = NULL;

    basicResp = OCSP_BASICRESP_new();

    *resp = OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bs);

    OCSP_BASICRESP_free(basicResp);
*/

    switch (status) {
        case mhOCSPRespnseStatusSuccessful:
            ocspStatus = OCSP_RESPONSE_STATUS_SUCCESSFUL;
            break;
        case mhOCSPRespnseStatusMalformedRequest:
            ocspStatus = OCSP_RESPONSE_STATUS_MALFORMEDREQUEST;
            break;
        case mhOCSPRespnseStatusInternalError:
            ocspStatus = OCSP_RESPONSE_STATUS_INTERNALERROR;
            break;
        case mhOCSPRespnseStatusTryLater:
            ocspStatus = OCSP_RESPONSE_STATUS_TRYLATER;
            break;
        case mhOCSPRespnseStatusSigRequired:
            ocspStatus = OCSP_RESPONSE_STATUS_SIGREQUIRED;
            break;
        case mhOCSPRespnseStatusUnauthorized:
            ocspStatus = OCSP_RESPONSE_STATUS_UNAUTHORIZED;
            break;
        default:
            /* Unsupported OCSP status */
            return 0;
    }

    *resp = OCSP_response_create(ocspStatus, NULL);

    return ret;
}

/**
 * OpenSSL callback, executed on the server when the client has enabled OCSP
 * support. If an OCSP responder was defined in the test, call it now with an
 * OCSP request to get an OCSP response that can be returned to the client.
 */
static int ocspStatusCallback(SSL *ssl, void *userdata)
{
    _mhClientCtx_t *cctx = userdata;
    const MockHTTP *mh = cctx->serv_ctx->mh;
    OCSP_RESPONSE *ocspResp;
    int result;
    int rspderlen;
    unsigned char *rspder = NULL;
    mhResponse_t *resp;
    mhAction_t dummy;
    mhRequest_t *req;
    req = _mhInitRequest(cctx->pool, RequestTypeOCSP);
    /* Nope, then see if there's a request matcher for all servers */
    if (matchRequest(req, &resp, &dummy, mh->ocspReqMatchers) == YES) {
        _mhBuildResponse(resp);

        if ((result = ocspCreateResponse(&ocspResp,
                                         resp->ocsp_response_status)) <= 0)
            return result;

        rspderlen = i2d_OCSP_RESPONSE(ocspResp, &rspder);
        if (rspderlen <= 0)
            return SSL_TLSEXT_ERR_ALERT_FATAL;

        SSL_set_tlsext_status_ocsp_resp(ssl, rspder, rspderlen);
        return SSL_TLSEXT_ERR_OK;
    }
    /* Couldn't find match */
    return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif  /* OPENSSL_NO_TLSEXT && OPENSSL_NO_OCSP */

/* Convert an ssl error into an apr status code for a specific context */
static apr_status_t status_from_ssl(sslCtx_t *ssl_ctx, int ret_code)
{
    int ssl_error = SSL_get_error(ssl_ctx->ssl, ret_code);

    if (ret_code > 0)
        return APR_SUCCESS;

    switch (ssl_error) {
        case 0:
            return APR_EOF;
        case SSL_ERROR_SYSCALL:
            return ssl_ctx->bio_status;
        default:
            return APR_EGENERAL;
    }
}

/**
 * Action: renegotiates a SSL session on client socket CCTX.
 * Returns APR_SUCCESS if the renegotiation handshake was successfull
 *         error if not.
 */
static apr_status_t renegotiateSSLSession(_mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    int ssl_result;
    apr_status_t status;

    /* TODO: check for APR_EAGAIN situation */
    ssl_result = SSL_renegotiate(ssl_ctx->ssl);
    status = status_from_ssl(ssl_ctx, ssl_result);

    if (status && !APR_STATUS_IS_EAGAIN(status)) {
        return status;
    }

    ssl_result = SSL_do_handshake(ssl_ctx->ssl);
    status = status_from_ssl(ssl_ctx, ssl_result);

    if (status && !APR_STATUS_IS_EAGAIN(status)) {
        return status;
    }

    ssl_ctx->renegotiate = YES;

    return APR_SUCCESS;
}

/**
 * Pool cleanup callback, cleans up the OpenSSL structures
 */
static apr_status_t cleanupSSL(void *baton)
{
    _mhClientCtx_t *cctx = baton;
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;

    if (ssl_ctx) {
        if (ssl_ctx->ssl) {
            SSL_clear(ssl_ctx->ssl);
            bio_meth_free(ssl_ctx->biom);
        }
        SSL_CTX_free(ssl_ctx->ctx);
    }

    return APR_SUCCESS;
}

/**
 * OpenSSL callback, accepts the client certificate.
 */
static int validateClientCertificate(int preverify_ok, X509_STORE_CTX *ctx)
{
    SSL *ssl = X509_STORE_CTX_get_ex_data(ctx,
                                          SSL_get_ex_data_X509_STORE_CTX_idx());
    _mhClientCtx_t *cctx = SSL_get_app_data(ssl);

    _mhLog(MH_VERBOSE, cctx->skt, "validate_client_certificate called, "
                                 "preverify: %d.\n", preverify_ok);
    /* Client cert is valid for now, can be validated later. */
    return 1;
}

/**
 * Inits the OpenSSL SSL structure.
 */
static apr_status_t initSSL(_mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;

    ssl_ctx->ssl = SSL_new(ssl_ctx->ctx);
    SSL_set_cipher_list(ssl_ctx->ssl, "ALL");
    SSL_set_bio(ssl_ctx->ssl, ssl_ctx->bio, ssl_ctx->bio);
    SSL_set_app_data(ssl_ctx->ssl, cctx);

    return APR_SUCCESS;
}

#ifndef OPENSSL_NO_TLSEXT
static int alpn_select_callback(SSL *ssl,
                                const unsigned char **out,
                                unsigned char *outlen,
                                const unsigned char *in,
                                unsigned int inlen,
                                void *arg)
{
  mhServCtx_t *serv_ctx = arg;
  const char *select = serv_ctx->alpn;
  apr_size_t select_sz = strlen(select);

  const unsigned char *p = in;

  while ((p + *p) < (in + inlen)) {

      if ((*p == select_sz)
          && !memcmp(p+1, select, select_sz)) {

          *out = (const unsigned char *)select;
          *outlen = (unsigned char)select_sz;
          return SSL_TLSEXT_ERR_OK;
      }

      p += *p + 1;
  }

  return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif  /* OPENSSL_NO_TLSEXT */

/**
 * Inits the OpenSSL context.
 */
static apr_status_t initSSLCtx(_mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = apr_pcalloc(cctx->pool, sizeof(*ssl_ctx));
    cctx->ssl_ctx = ssl_ctx;
    ssl_ctx->bio_status = APR_SUCCESS;

    _mhLog(MH_VERBOSE, cctx->skt, "Initializing SSL context.\n");

    /* Init OpenSSL globally */
    if (!init_done)
    {
        ERR_load_crypto_strings();
        SSL_load_error_strings();
        SSL_library_init();
        OpenSSL_add_all_algorithms();
        init_done = 1;
    }

    if (!ssl_ctx->ctx) {
        X509_STORE *store;
        const char *certfile;
        int i;

        /* Configure supported protocol versions */
        ssl_ctx->ctx = SSL_CTX_new(SSLv23_server_method());
        if (! (cctx->protocols & mhProtoSSLv2))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_SSLv2);
        if (! (cctx->protocols & mhProtoSSLv3))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_SSLv3);
        if (! (cctx->protocols & mhProtoTLSv1))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_TLSv1);
#ifdef SSL_OP_NO_TLSv1_1
        if (! (cctx->protocols & mhProtoTLSv11))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_TLSv1_1);
#endif
#ifdef SSL_OP_NO_TLSv1_2
        if (! (cctx->protocols & mhProtoTLSv12))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef SSL_OP_NO_TLSv1_3
        if (! (cctx->protocols & mhProtoTLSv13))
            SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_TLSv1_3);
#endif

#if OPENSSL_VERSION_NUMBER >= 0x10002000L /* >= 1.0.2 */
#  ifndef OPENSSL_NO_TLSEXT
        if (cctx->serv_ctx->alpn) {
            SSL_CTX_set_alpn_select_cb(ssl_ctx->ctx,
                                       alpn_select_callback,
                                       cctx->serv_ctx);
        }
#  endif
#endif

        if (cctx->protocols == mhProtoSSLv2) {
            /* In recent versions of OpenSSL, SSLv2 has been disabled by removing
               all SSLv2 ciphers from the cipher string.
               If SSLv2 is the only protocol this test wants to be enabled,
               re-add the SSLv2 ciphers. */
            SSL_CTX_set_cipher_list(ssl_ctx->ctx, "SSLv2");
            /* ignore result */
        }

        /* Always set this callback, even if no passphrase is set. Otherwise
           OpenSSL will prompt the user to provide a passphrase if one is
           needed. */
        SSL_CTX_set_default_passwd_cb(ssl_ctx->ctx, pem_passwd_cb);
        SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx->ctx, cctx);
        if (SSL_CTX_use_PrivateKey_file(ssl_ctx->ctx, cctx->keyFile,
                                        SSL_FILETYPE_PEM) != 1) {
            _mhLog(MH_VERBOSE, cctx->skt,
                   "Cannot load private key from file '%s'\n", cctx->keyFile);
            return APR_EGENERAL;
        }

        /* Set server certificate, add ca certificates if provided. */
        certfile = APR_ARRAY_IDX(cctx->certFiles, 0, const char *);
        if (SSL_CTX_use_certificate_file(ssl_ctx->ctx, certfile,
                                         SSL_FILETYPE_PEM) != 1) {
            _mhLog(MH_VERBOSE, cctx->skt,
                   "Cannot load certificatefrom file '%s'\n", certfile);
            return APR_EGENERAL;
        }

        store = SSL_CTX_get_cert_store(ssl_ctx->ctx);
        for (i = 1; i < cctx->certFiles->nelts; i++) {
            FILE *fp;
            certfile = APR_ARRAY_IDX(cctx->certFiles, i, const char *);
            fp = fopen(certfile, "r");
            if (fp) {
                X509 *ssl_cert = PEM_read_X509(fp, NULL, NULL, NULL);
                fclose(fp);

                X509_STORE_add_cert(store, ssl_cert);
                SSL_CTX_add_extra_chain_cert(ssl_ctx->ctx, ssl_cert);
            }
        }

        /* Check if the server needs to ask the client to send a certificate
           during handshake. */
        switch (cctx->clientCert) {
            case mhCCVerifyNone:
                break;
            case mhCCVerifyPeer:
                SSL_CTX_set_verify(ssl_ctx->ctx, SSL_VERIFY_PEER,
                                   validateClientCertificate);
                break;
            case mhCCVerifyFailIfNoPeerSet:
                SSL_CTX_set_verify(ssl_ctx->ctx,
                                   SSL_VERIFY_PEER |
                                     SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
                                   validateClientCertificate);
                break;
            default:
                break;
        }

#if !defined(OPENSSL_NO_TLSEXT) && !defined(OPENSSL_NO_OCSP)
        if (cctx->ocspEnabled) {
            SSL_CTX_set_tlsext_status_cb(ssl_ctx->ctx, ocspStatusCallback);
            SSL_CTX_set_tlsext_status_arg(ssl_ctx->ctx, cctx);
        }
#endif

        SSL_CTX_set_mode(ssl_ctx->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
                                       | SSL_MODE_ENABLE_PARTIAL_WRITE
                                       | SSL_MODE_AUTO_RETRY);

        ssl_ctx->bio = BIO_new(bio_meth_apr_socket_new());
        bio_set_data(ssl_ctx->bio, cctx);
        initSSL(cctx);

        apr_pool_cleanup_register(cctx->pool, cctx,
                                  cleanupSSL, apr_pool_cleanup_null);
    }
    return APR_SUCCESS;
}

/**
 * Callback, encrypts data of length LEN in buffer DATA and writes to the socket.
 */
static apr_status_t
sslSocketWrite(_mhClientCtx_t *cctx, const char *data, apr_size_t *len)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    int result, ssl_err;

    if (*len == 0)
        return APR_SUCCESS;

    ssl_ctx->bio_status = APR_SUCCESS;
    result = SSL_write(ssl_ctx->ssl, data, *len);
    if (result > 0) {
        *len = result;
        return APR_SUCCESS;
    }

    ssl_err = SSL_get_error(ssl_ctx->ssl, result);
    switch (ssl_err) {
      case SSL_ERROR_ZERO_RETURN:
        *len = 0;
        return APR_EOF; /* Clean SSL shutdown */
      case SSL_ERROR_SYSCALL:
        /* error in bio_bucket_read, probably APR_EAGAIN or APR_EOF */
        *len = 0;
        return ssl_ctx->bio_status;
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE:
        *len = 0;
        return APR_EAGAIN;
      case SSL_ERROR_SSL:
        /* When the client kills the connection, we can expect protocol
        errors... Let's just return that we didn't see an error,
        but that the connection was closed. */
        *len = 0;
        return APR_EOF;
      default:
        *len = 0;
        _mhLog(MH_VERBOSE, cctx->skt,
               "sslSocketWrite SSL Error %d: ", ssl_err);
#if MH_VERBOSE
        ERR_print_errors_fp(stderr);
#endif
        return APR_EGENERAL;
    }


    if (result == 0)
        return APR_EAGAIN;



    return ssl_ctx->bio_status ? ssl_ctx->bio_status : APR_EGENERAL;
}

/**
 * Callback, reads data and decrypt it. Decrypted buffer of length LEN is
 * returned in DATA.
 */
static apr_status_t
sslSocketRead(apr_socket_t *skt, void *baton, char *data, apr_size_t *len)
{
    sslCtx_t *ssl_ctx = baton;
    int result;

    if (*len > sizeof(ssl_ctx->read_buffer))
      *len = sizeof(ssl_ctx->read_buffer);

    ssl_ctx->bio_status = APR_SUCCESS;
    result = SSL_read(ssl_ctx->ssl, ssl_ctx->read_buffer, *len);
    if (result > 0) {
        *len = result;
        memcpy(data, ssl_ctx->read_buffer, *len);
        return APR_SUCCESS;
    } else {
        int ssl_err;

        ssl_err = SSL_get_error(ssl_ctx->ssl, result);
        switch (ssl_err) {
            case SSL_ERROR_ZERO_RETURN:
                *len = 0;
                return APR_EOF; /* Clean SSL shutdown */
            case SSL_ERROR_SYSCALL:
                /* error in bio_bucket_read, probably APR_EAGAIN or APR_EOF */
                *len = 0;
                return ssl_ctx->bio_status;
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
                *len = 0;
                return APR_EAGAIN;
            case SSL_ERROR_SSL:
                /* When the client kills the connection, we can expect protocol
                   errors... Let's just return that we didn't see an error,
                   but that the connection was closed. */
                *len = 0;
                return APR_EOF;
            default:
                *len = 0;
                _mhLog(MH_VERBOSE, skt,
                          "sslSocketRead SSL Error %d: ", ssl_err);
#if MH_VERBOSE
                ERR_print_errors_fp(stderr);
#endif
                return APR_EGENERAL;
        }
    }

    /* not reachable */
    return APR_EGENERAL;
}

static apr_status_t sslSocketShutdown(_mhClientCtx_t *cctx,
                              apr_shutdown_how_e how)
{
    if (how == APR_SHUTDOWN_READ
        || how == APR_SHUTDOWN_READWRITE) {
        SSL_shutdown(cctx->ssl_ctx->ssl);
        return APR_SUCCESS;
    }
    return APR_ENOTIMPL;
}

static void appendSSLErrMessage(const MockHTTP *mh, long result)
{
    apr_size_t startpos = strlen(mh->errmsg);
    ERR_error_string(result, mh->errmsg + startpos);
#if MH_VERBOSE
    ERR_print_errors_fp(stderr);
#endif
}

/******************************************************************************/
/* Connection-level matchers: define criteria to match different aspects of a */
/* HTTP or HTTPS connection.                                                  */
/******************************************************************************/

/**
 * Builder callback, verifies if the client certificate is valid (its issuer
 * is in the provided list of trusted certificates).
 */
bool _mhClientcert_valid_matcher(const mhConnMatcherBldr_t *mp,
                                 const _mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    X509 *peer;

    /* Check client certificate */
    peer = SSL_get_peer_certificate(ssl_ctx->ssl);
    if (peer) {
        long result = SSL_get_verify_result(ssl_ctx->ssl);
        if (result == X509_V_OK) {
            /* The client sent a certificate which verified OK */
            return YES;
        } else {
//            appendSSLErrMessage(mh, result);
        }
        /* TODO: add to error message */
        _mhLog(MH_VERBOSE, cctx->skt, "No client certificate was received.\n");
    }
    return NO;
}

/**
 * Builder callback, verifies if the client certificate CN equals a certain
 * string.
 */
bool _mhClientcertcn_matcher(const mhConnMatcherBldr_t *mp,
                             const _mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    X509 *peer;
    const char *clientCN = mp->baton;

    /* Check client certificate */
    peer = SSL_get_peer_certificate(ssl_ctx->ssl);
    if (peer) {
        char buf[1024];
        int ret;
        X509_NAME *subject = X509_get_subject_name(peer);

        ret = X509_NAME_get_text_by_NID(subject,
                                        NID_commonName,
                                        buf, 1024);
        if (ret != -1 && strcmp(clientCN, buf) == 0) {
            return YES;
        }

        /* TODO: add to error message */
        _mhLog(MH_VERBOSE, cctx->skt, "Client certificate common name "
               "\"%s\" doesn't match expected \"%s\".\n", buf, clientCN);
        return NO;
    } else {
        /* TODO: add to error message */
        _mhLog(MH_VERBOSE, cctx->skt, "No client certificate was received.\n");
        return NO;
    }
}

/**
 * Performs the SSL handshake on the connection client/proxy <-> server, can be
 * called multiple times until successful.
 * Returns APR_EAGAIN when handshake in progress.
 *         APR_SUCCESS when handshake finished
 *         error in case of error during handshake.
 */
static apr_status_t sslHandshake(_mhClientCtx_t *cctx)
{
    sslCtx_t *ssl_ctx = cctx->ssl_ctx;
    int result;

    if (ssl_ctx->renegotiate) {
        if (!SSL_do_handshake(ssl_ctx->ssl))
            return APR_EGENERAL;
    }

    if (ssl_ctx->handshake_done)
        return APR_SUCCESS;

    /* Initial SSL handshake */
    result = SSL_accept(ssl_ctx->ssl);
    if (result == 1) {
        _mhLog(MH_VERBOSE, cctx->skt, "Handshake successful.\n");
        ssl_ctx->handshake_done = YES;

        return APR_SUCCESS;
    } else {
        int ssl_err;
        unsigned long l = ERR_peek_error();

        ssl_err = SSL_get_error(ssl_ctx->ssl, result);
        switch (ssl_err) {
            case SSL_ERROR_WANT_READ:
            case SSL_ERROR_WANT_WRITE:
                return APR_EAGAIN;
            case SSL_ERROR_SYSCALL:
                return ssl_ctx->bio_status; /* Usually APR_EAGAIN */
            default:
                {
                    int lib = ERR_GET_LIB(l);
                    int func = ERR_GET_FUNC(l);
                    int reason = ERR_GET_REASON(l);

                    if (lib == ERR_LIB_SSL
                        && (reason == SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE
                            || reason == ERR_R_INTERNAL_ERROR)) {
                        /* The server shouldn't fail for this...

                           We test the client. Go on, and report the problem
                           there */
                        return APR_EAGAIN;
                    }

                    _mhLog(MH_VERBOSE, cctx->skt,
                           "SSL Error %d: Library=%d, Function=%d, Reason=%d",
                           ssl_err, lib, func, reason);
#if MH_VERBOSE
                    ERR_print_errors_fp(stderr);
#endif
                }
                return APR_EGENERAL;
        }
    }

    /* not reachable */
    return APR_EGENERAL;
}

#else /* TODO: OpenSSL not available => empty implementations */

#endif

