blob: ceba3187cb554359cbba89b4b5e19d28868f9d31 [file] [log] [blame]
/* ====================================================================
* 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 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, Reason=%d",
ssl_err, lib, 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