#include "ssl_private.h"
#include "apr_date.h"
typedef struct {
SSL *pssl;
BIO *pbioRead;
BIO *pbioWrite;
ap_filter_t *pInputFilter;
ap_filter_t *pOutputFilter;
int nobuffer;
SSLConnRec *config;
} ssl_filter_ctx_t;
typedef struct {
ssl_filter_ctx_t *filter_ctx;
conn_rec *c;
apr_bucket_brigade *bb;
apr_size_t length;
char buffer[AP_IOBUFSIZE];
apr_size_t blen;
apr_status_t rc;
} bio_filter_out_ctx_t;
static bio_filter_out_ctx_t *bio_filter_out_ctx_new(ssl_filter_ctx_t *filter_ctx,
conn_rec *c)
{
bio_filter_out_ctx_t *outctx = apr_palloc(c->pool, sizeof(*outctx));
outctx->filter_ctx = filter_ctx;
outctx->c = c;
outctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
outctx->blen = 0;
outctx->length = 0;
return outctx;
}
static int bio_filter_out_flush(BIO *bio)
{
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr);
apr_bucket *e;
if (!(outctx->blen || outctx->length)) {
outctx->rc = APR_SUCCESS;
return 1;
}
if (outctx->blen) {
e = apr_bucket_transient_create(outctx->buffer, outctx->blen,
outctx->bb->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(outctx->bb, e);
outctx->blen = 0;
}
outctx->length = 0;
e = apr_bucket_flush_create(outctx->bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next,
outctx->bb);
if (outctx->rc == APR_SUCCESS && outctx->c->aborted) {
outctx->rc = APR_ECONNRESET;
}
return (outctx->rc == APR_SUCCESS) ? 1 : -1;
}
static int bio_filter_create(BIO *bio)
{
bio->shutdown = 1;
bio->init = 1;
bio->num = -1;
bio->ptr = NULL;
return 1;
}
static int bio_filter_destroy(BIO *bio)
{
if (bio == NULL) {
return 0;
}
return 1;
}
static int bio_filter_out_read(BIO *bio, char *out, int outl)
{
return -1;
}
static int bio_filter_out_write(BIO *bio, const char *in, int inl)
{
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr);
if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
outctx->rc = APR_ECONNABORTED;
return -1;
}
BIO_clear_retry_flags(bio);
if (!outctx->length && (inl + outctx->blen < sizeof(outctx->buffer)) &&
!outctx->filter_ctx->nobuffer) {
memcpy(&outctx->buffer[outctx->blen], in, inl);
outctx->blen += inl;
}
else {
apr_bucket *bucket = apr_bucket_transient_create(in, inl,
outctx->bb->bucket_alloc);
outctx->length += inl;
APR_BRIGADE_INSERT_TAIL(outctx->bb, bucket);
if (bio_filter_out_flush(bio) < 0) {
return -1;
}
}
return inl;
}
static long bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
long ret = 1;
char **pptr;
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)(bio->ptr);
switch (cmd) {
case BIO_CTRL_RESET:
outctx->blen = outctx->length = 0;
break;
case BIO_CTRL_EOF:
ret = (long)((outctx->blen + outctx->length) == 0);
break;
case BIO_C_SET_BUF_MEM_EOF_RETURN:
outctx->blen = outctx->length = (apr_size_t)num;
break;
case BIO_CTRL_INFO:
ret = (long)(outctx->blen + outctx->length);
if (ptr) {
pptr = (char **)ptr;
*pptr = (char *)&(outctx->buffer[0]);
}
break;
case BIO_CTRL_GET_CLOSE:
ret = (long)bio->shutdown;
break;
case BIO_CTRL_SET_CLOSE:
bio->shutdown = (int)num;
break;
case BIO_CTRL_WPENDING:
ret = 0L;
break;
case BIO_CTRL_PENDING:
ret = (long)(outctx->blen + outctx->length);
break;
case BIO_CTRL_FLUSH:
ret = bio_filter_out_flush(bio);
break;
case BIO_CTRL_DUP:
ret = 1;
break;
case BIO_C_SET_BUF_MEM:
case BIO_C_GET_BUF_MEM_PTR:
case BIO_CTRL_PUSH:
case BIO_CTRL_POP:
default:
ret = 0;
break;
}
return ret;
}
static int bio_filter_out_gets(BIO *bio, char *buf, int size)
{
return -1;
}
static int bio_filter_out_puts(BIO *bio, const char *str)
{
return -1;
}
static BIO_METHOD bio_filter_out_method = {
BIO_TYPE_MEM,
"APR output filter",
bio_filter_out_write,
bio_filter_out_read,
bio_filter_out_puts,
bio_filter_out_gets,
bio_filter_out_ctrl,
bio_filter_create,
bio_filter_destroy,
#ifdef OPENSSL_VERSION_NUMBER
NULL
#endif
};
typedef struct {
int length;
char *value;
} char_buffer_t;
typedef struct {
SSL *ssl;
BIO *bio_out;
ap_filter_t *f;
apr_status_t rc;
ap_input_mode_t mode;
apr_read_type_e block;
apr_bucket_brigade *bb;
char_buffer_t cbuf;
apr_pool_t *pool;
char buffer[AP_IOBUFSIZE];
ssl_filter_ctx_t *filter_ctx;
} bio_filter_in_ctx_t;
static int char_buffer_read(char_buffer_t *buffer, char *in, int inl)
{
if (!buffer->length) {
return 0;
}
if (buffer->length > inl) {
memmove(in, buffer->value, inl);
buffer->value += inl;
buffer->length -= inl;
}
else {
memmove(in, buffer->value, buffer->length);
inl = buffer->length;
buffer->value = NULL;
buffer->length = 0;
}
return inl;
}
static int char_buffer_write(char_buffer_t *buffer, char *in, int inl)
{
buffer->value = in;
buffer->length = inl;
return inl;
}
static apr_status_t brigade_consume(apr_bucket_brigade *bb,
apr_read_type_e block,
char *c, apr_size_t *len)
{
apr_size_t actual = 0;
apr_status_t status = APR_SUCCESS;
while (!APR_BRIGADE_EMPTY(bb)) {
apr_bucket *b = APR_BRIGADE_FIRST(bb);
const char *str;
apr_size_t str_len;
apr_size_t consume;
if (APR_BUCKET_IS_EOS(b)) {
status = APR_EOF;
break;
}
status = apr_bucket_read(b, &str, &str_len, block);
if (status != APR_SUCCESS) {
if (APR_STATUS_IS_EOF(status)) {
apr_bucket_delete(b);
continue;
}
break;
}
if (str_len > 0) {
block = APR_NONBLOCK_READ;
consume = (str_len + actual > *len) ? *len - actual : str_len;
memcpy(c, str, consume);
c += consume;
actual += consume;
if (consume >= b->length) {
apr_bucket_delete(b);
}
else {
b->start += consume;
b->length -= consume;
}
}
else if (b->length == 0) {
apr_bucket_delete(b);
}
if (actual >= *len) {
break;
}
}
*len = actual;
return status;
}
static int bio_filter_in_read(BIO *bio, char *in, int inlen)
{
apr_size_t inl = inlen;
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)(bio->ptr);
apr_read_type_e block = inctx->block;
inctx->rc = APR_SUCCESS;
if (!in)
return 0;
if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
inctx->rc = APR_ECONNABORTED;
return -1;
}
if (bio_filter_out_flush(inctx->bio_out) < 0) {
bio_filter_out_ctx_t *outctx = inctx->bio_out->ptr;
inctx->rc = outctx->rc;
return -1;
}
BIO_clear_retry_flags(bio);
if (!inctx->bb) {
inctx->rc = APR_EOF;
return -1;
}
if (APR_BRIGADE_EMPTY(inctx->bb)) {
inctx->rc = ap_get_brigade(inctx->f->next, inctx->bb,
AP_MODE_READBYTES, block,
inl);
if (APR_STATUS_IS_EAGAIN(inctx->rc) || APR_STATUS_IS_EINTR(inctx->rc)
|| (inctx->rc == APR_SUCCESS && APR_BRIGADE_EMPTY(inctx->bb))) {
BIO_set_retry_read(bio);
return -1;
}
if (inctx->rc != APR_SUCCESS) {
apr_brigade_cleanup(inctx->bb);
inctx->bb = NULL;
return -1;
}
}
inctx->rc = brigade_consume(inctx->bb, block, in, &inl);
if (inctx->rc == APR_SUCCESS) {
return (int)inl;
}
if (APR_STATUS_IS_EAGAIN(inctx->rc)
|| APR_STATUS_IS_EINTR(inctx->rc)) {
BIO_set_retry_read(bio);
return (int)inl;
}
apr_brigade_cleanup(inctx->bb);
inctx->bb = NULL;
if (APR_STATUS_IS_EOF(inctx->rc) && inl) {
return (int)inl;
}
return -1;
}
static BIO_METHOD bio_filter_in_method = {
BIO_TYPE_MEM,
"APR input filter",
NULL,
bio_filter_in_read,
NULL,
NULL,
NULL,
bio_filter_create,
bio_filter_destroy,
#ifdef OPENSSL_VERSION_NUMBER
NULL
#endif
};
static apr_status_t ssl_io_input_read(bio_filter_in_ctx_t *inctx,
char *buf,
apr_size_t *len)
{
apr_size_t wanted = *len;
apr_size_t bytes = 0;
int rc;
*len = 0;
if ((bytes = char_buffer_read(&inctx->cbuf, buf, wanted))) {
*len = bytes;
if (inctx->mode == AP_MODE_SPECULATIVE) {
if (inctx->cbuf.length > 0) {
inctx->cbuf.value -= bytes;
inctx->cbuf.length += bytes;
} else {
char_buffer_write(&inctx->cbuf, buf, (int)bytes);
}
return APR_SUCCESS;
}
if (*len >= wanted) {
return APR_SUCCESS;
}
if (inctx->mode == AP_MODE_GETLINE) {
if (memchr(buf, APR_ASCII_LF, *len)) {
return APR_SUCCESS;
}
}
else {
inctx->block = APR_NONBLOCK_READ;
}
}
while (1) {
if (!inctx->filter_ctx->pssl) {
if (inctx->rc == APR_SUCCESS) {
inctx->rc = APR_EGENERAL;
}
break;
}
rc = SSL_read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes);
if (rc > 0) {
*len += rc;
if (inctx->mode == AP_MODE_SPECULATIVE) {
char_buffer_write(&inctx->cbuf, buf, rc);
}
return inctx->rc;
}
else if (rc == 0) {
if (APR_STATUS_IS_EAGAIN(inctx->rc)
|| APR_STATUS_IS_EINTR(inctx->rc)) {
if (*len > 0) {
inctx->rc = APR_SUCCESS;
break;
}
if (inctx->block == APR_NONBLOCK_READ) {
break;
}
}
else {
if (*len > 0) {
inctx->rc = APR_SUCCESS;
}
else {
inctx->rc = APR_EOF;
}
break;
}
}
else {
int ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc);
conn_rec *c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl);
if (ssl_err == SSL_ERROR_WANT_READ) {
inctx->rc = APR_EAGAIN;
if (*len > 0) {
inctx->rc = APR_SUCCESS;
break;
}
if (inctx->block == APR_NONBLOCK_READ) {
break;
}
continue;
}
else if (ssl_err == SSL_ERROR_SYSCALL) {
if (APR_STATUS_IS_EAGAIN(inctx->rc)
|| APR_STATUS_IS_EINTR(inctx->rc)) {
if (*len > 0) {
inctx->rc = APR_SUCCESS;
break;
}
if (inctx->block == APR_NONBLOCK_READ) {
break;
}
continue;
}
else {
ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c,
"SSL input filter read failed.");
}
}
else {
ap_log_cerror(APLOG_MARK, APLOG_INFO, inctx->rc, c,
"SSL library error %d reading data", ssl_err);
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, mySrvFromConn(c));
}
if (inctx->rc == APR_SUCCESS) {
inctx->rc = APR_EGENERAL;
}
break;
}
}
return inctx->rc;
}
static apr_status_t ssl_io_input_getline(bio_filter_in_ctx_t *inctx,
char *buf,
apr_size_t *len)
{
const char *pos = NULL;
apr_status_t status;
apr_size_t tmplen = *len, buflen = *len, offset = 0;
*len = 0;
while (tmplen > 0) {
status = ssl_io_input_read(inctx, buf + offset, &tmplen);
if (status != APR_SUCCESS) {
if (APR_STATUS_IS_EAGAIN(status) && (*len > 0)) {
char_buffer_write(&inctx->cbuf, buf, *len);
}
return status;
}
*len += tmplen;
if ((pos = memchr(buf, APR_ASCII_LF, *len))) {
break;
}
offset += tmplen;
tmplen = buflen - offset;
}
if (pos) {
char *value;
int length;
apr_size_t bytes = pos - buf;
bytes += 1;
value = buf + bytes;
length = *len - bytes;
char_buffer_write(&inctx->cbuf, value, length);
*len = bytes;
}
return APR_SUCCESS;
}
static apr_status_t ssl_filter_write(ap_filter_t *f,
const char *data,
apr_size_t len)
{
ssl_filter_ctx_t *filter_ctx = f->ctx;
bio_filter_out_ctx_t *outctx;
int res;
if (filter_ctx->pssl == NULL) {
return APR_EGENERAL;
}
outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr;
res = SSL_write(filter_ctx->pssl, (unsigned char *)data, len);
if (res < 0) {
int ssl_err = SSL_get_error(filter_ctx->pssl, res);
conn_rec *c = (conn_rec*)SSL_get_app_data(outctx->filter_ctx->pssl);
if (ssl_err == SSL_ERROR_WANT_WRITE) {
outctx->rc = APR_EAGAIN;
}
else if (ssl_err == SSL_ERROR_SYSCALL) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c,
"SSL output filter write failed.");
}
else {
ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c,
"SSL library error %d writing data", ssl_err);
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, mySrvFromConn(c));
}
if (outctx->rc == APR_SUCCESS) {
outctx->rc = APR_EGENERAL;
}
}
else if ((apr_size_t)res != len) {
conn_rec *c = f->c;
char *reason = "reason unknown";
if (SSL_total_renegotiations(filter_ctx->pssl)) {
reason = "likely due to failed renegotiation";
}
ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c,
"failed to write %" APR_SSIZE_T_FMT
" of %" APR_SIZE_T_FMT " bytes (%s)",
len - (apr_size_t)res, len, reason);
outctx->rc = APR_EGENERAL;
}
return outctx->rc;
}
#define HTTP_ON_HTTPS_PORT \
"GET /" CRLF
#define HTTP_ON_HTTPS_PORT_BUCKET(alloc) \
apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, \
sizeof(HTTP_ON_HTTPS_PORT) - 1, \
alloc)
static void ssl_io_filter_disable(SSLConnRec *sslconn, ap_filter_t *f)
{
bio_filter_in_ctx_t *inctx = f->ctx;
SSL_free(inctx->ssl);
sslconn->ssl = NULL;
inctx->ssl = NULL;
inctx->filter_ctx->pssl = NULL;
}
static apr_status_t ssl_io_filter_error(ap_filter_t *f,
apr_bucket_brigade *bb,
apr_status_t status)
{
SSLConnRec *sslconn = myConnConfig(f->c);
apr_bucket *bucket;
switch (status) {
case HTTP_BAD_REQUEST:
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, f->c,
"SSL handshake failed: HTTP spoken on HTTPS port; "
"trying to send HTML error page");
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, sslconn->server);
sslconn->non_ssl_request = 1;
ssl_io_filter_disable(sslconn, f);
bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc);
break;
default:
return status;
}
APR_BRIGADE_INSERT_TAIL(bb, bucket);
bucket = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, bucket);
return APR_SUCCESS;
}
static const char ssl_io_filter[] = "SSL/TLS Filter";
static const char ssl_io_buffer[] = "SSL/TLS Buffer";
static apr_status_t ssl_filter_io_shutdown(ssl_filter_ctx_t *filter_ctx,
conn_rec *c,
int abortive)
{
SSL *ssl = filter_ctx->pssl;
const char *type = "";
SSLConnRec *sslconn = myConnConfig(c);
int shutdown_type;
if (!ssl) {
return APR_SUCCESS;
}
if (abortive) {
shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
type = "abortive";
}
else switch (sslconn->shutdown_type) {
case SSL_SHUTDOWN_TYPE_UNCLEAN:
shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
type = "unclean";
break;
case SSL_SHUTDOWN_TYPE_ACCURATE:
shutdown_type = 0;
type = "accurate";
break;
default:
shutdown_type = SSL_RECEIVED_SHUTDOWN;
type = "standard";
break;
}
SSL_set_shutdown(ssl, shutdown_type);
SSL_smart_shutdown(ssl);
if (mySrvFromConn(c)->loglevel >= APLOG_INFO) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"Connection closed to child %ld with %s shutdown "
"(server %s)",
c->id, type, ssl_util_vhostid(c->pool, mySrvFromConn(c)));
}
if (sslconn->client_cert) {
X509_free(sslconn->client_cert);
sslconn->client_cert = NULL;
}
SSL_free(ssl);
sslconn->ssl = NULL;
filter_ctx->pssl = NULL;
if (abortive) {
c->aborted = 1;
}
return APR_SUCCESS;
}
static apr_status_t ssl_io_filter_cleanup(void *data)
{
ssl_filter_ctx_t *filter_ctx = data;
if (filter_ctx->pssl) {
conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
SSLConnRec *sslconn = myConnConfig(c);
SSL_free(filter_ctx->pssl);
sslconn->ssl = filter_ctx->pssl = NULL;
}
return APR_SUCCESS;
}
static int ssl_io_filter_connect(ssl_filter_ctx_t *filter_ctx)
{
conn_rec *c = (conn_rec *)SSL_get_app_data(filter_ctx->pssl);
SSLConnRec *sslconn = myConnConfig(c);
SSLSrvConfigRec *sc;
X509 *cert;
int n;
int ssl_err;
long verify_result;
server_rec *server;
if (SSL_is_init_finished(filter_ctx->pssl)) {
return APR_SUCCESS;
}
server = sslconn->server;
if (sslconn->is_proxy) {
const char *hostname_note;
sc = mySrvConfig(server);
if ((n = SSL_connect(filter_ctx->pssl)) <= 0) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"SSL Proxy connect failed");
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, server);
ssl_filter_io_shutdown(filter_ctx, c, 1);
apr_table_set(c->notes, "SSL_connect_rv", "err");
return HTTP_BAD_GATEWAY;
}
if (sc->proxy_ssl_check_peer_expire == SSL_ENABLED_TRUE) {
cert = SSL_get_peer_certificate(filter_ctx->pssl);
if (!cert
|| (X509_cmp_current_time(
X509_get_notBefore(cert)) >= 0)
|| (X509_cmp_current_time(
X509_get_notAfter(cert)) <= 0)) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"SSL Proxy: Peer certificate is expired");
if (cert) {
X509_free(cert);
}
ssl_filter_io_shutdown(filter_ctx, c, 1);
apr_table_set(c->notes, "SSL_connect_rv", "err");
return HTTP_BAD_GATEWAY;
}
X509_free(cert);
}
if ((sc->proxy_ssl_check_peer_cn == SSL_ENABLED_TRUE)
&& ((hostname_note =
apr_table_get(c->notes, "proxy-request-hostname")) != NULL)) {
const char *hostname;
hostname = ssl_var_lookup(NULL, server, c, NULL,
"SSL_CLIENT_S_DN_CN");
apr_table_unset(c->notes, "proxy-request-hostname");
if (strcasecmp(hostname, hostname_note)) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"SSL Proxy: Peer certificate CN mismatch:"
" Certificate CN: %s Requested hostname: %s",
hostname, hostname_note);
ssl_filter_io_shutdown(filter_ctx, c, 1);
apr_table_set(c->notes, "SSL_connect_rv", "err");
return HTTP_BAD_GATEWAY;
}
}
apr_table_set(c->notes, "SSL_connect_rv", "ok");
return APR_SUCCESS;
}
if ((n = SSL_accept(filter_ctx->pssl)) <= 0) {
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)
(filter_ctx->pbioRead->ptr);
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)
(filter_ctx->pbioWrite->ptr);
apr_status_t rc = inctx->rc ? inctx->rc : outctx->rc ;
ssl_err = SSL_get_error(filter_ctx->pssl, n);
if (ssl_err == SSL_ERROR_ZERO_RETURN) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c,
"SSL handshake stopped: connection was closed");
}
else if (ssl_err == SSL_ERROR_WANT_READ) {
outctx->rc = APR_EAGAIN;
return SSL_ERROR_WANT_READ;
}
else if (ERR_GET_LIB(ERR_peek_error()) == ERR_LIB_SSL &&
ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) {
return HTTP_BAD_REQUEST;
}
else if (ssl_err == SSL_ERROR_SYSCALL) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c,
"SSL handshake interrupted by system "
"[Hint: Stop button pressed in browser?!]");
}
else {
ap_log_cerror(APLOG_MARK, APLOG_INFO, rc, c,
"SSL library error %d in handshake "
"(server %s)", ssl_err,
ssl_util_vhostid(c->pool, server));
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, server);
}
if (inctx->rc == APR_SUCCESS) {
inctx->rc = APR_EGENERAL;
}
return ssl_filter_io_shutdown(filter_ctx, c, 1);
}
sc = mySrvConfig(sslconn->server);
verify_result = SSL_get_verify_result(filter_ctx->pssl);
if ((verify_result != X509_V_OK) ||
sslconn->verify_error)
{
if (ssl_verify_error_is_optional(verify_result) &&
(sc->server->auth.verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA))
{
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"SSL client authentication failed, "
"accepting certificate based on "
"\"SSLVerifyClient optional_no_ca\" "
"configuration");
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, server);
}
else {
const char *error = sslconn->verify_error ?
sslconn->verify_error :
X509_verify_cert_error_string(verify_result);
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"SSL client authentication failed: %s",
error ? error : "unknown");
ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, server);
return ssl_filter_io_shutdown(filter_ctx, c, 1);
}
}
if ((cert = SSL_get_peer_certificate(filter_ctx->pssl))) {
if (sslconn->client_cert) {
X509_free(sslconn->client_cert);
}
sslconn->client_cert = cert;
sslconn->client_dn = NULL;
}
if ((sc->server->auth.verify_mode == SSL_CVERIFY_REQUIRE) &&
!sslconn->client_cert)
{
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c,
"No acceptable peer certificate available");
return ssl_filter_io_shutdown(filter_ctx, c, 1);
}
return APR_SUCCESS;
}
#define SWITCH_STATUS_LINE "HTTP/1.1 101 Switching Protocols"
#define UPGRADE_HEADER "Upgrade: TLS/1.0, HTTP/1.1"
#define CONNECTION_HEADER "Connection: Upgrade"
static apr_status_t ssl_io_filter_Upgrade(ap_filter_t *f,
apr_bucket_brigade *bb)
{
const char *upgrade;
apr_bucket_brigade *upgradebb;
request_rec *r = f->r;
SSLConnRec *sslconn;
apr_status_t rv;
apr_bucket *b;
SSL *ssl;
ap_remove_output_filter(f);
upgrade = apr_table_get(r->headers_in, "Upgrade");
if (upgrade == NULL
|| strcmp(ap_getword(r->pool, &upgrade, ','), "TLS/1.0")) {
return ap_pass_brigade(f->next, bb);
}
apr_table_unset(r->headers_out, "Upgrade");
upgradebb = apr_brigade_create(r->pool, f->c->bucket_alloc);
ap_fputstrs(f->next, upgradebb, SWITCH_STATUS_LINE, CRLF,
UPGRADE_HEADER, CRLF, CONNECTION_HEADER, CRLF, CRLF, NULL);
b = apr_bucket_flush_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(upgradebb, b);
rv = ap_pass_brigade(f->next, upgradebb);
if (rv) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"could not send interim 101 Upgrade response");
return AP_FILTER_ERROR;
}
ssl_init_ssl_connection(f->c);
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Awaiting re-negotiation handshake");
sslconn = myConnConfig(f->c);
ssl = sslconn->ssl;
SSL_set_accept_state(ssl);
SSL_do_handshake(ssl);
if (SSL_get_state(ssl) != SSL_ST_OK) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"TLS Upgrade handshake failed: "
"Not accepted by client!?");
return AP_FILTER_ERROR;
}
return ap_pass_brigade(f->c->output_filters, bb);
}
static apr_status_t ssl_io_filter_input(ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
apr_status_t status;
bio_filter_in_ctx_t *inctx = f->ctx;
apr_size_t len = sizeof(inctx->buffer);
int is_init = (mode == AP_MODE_INIT);
if (f->c->aborted) {
apr_bucket *bucket = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, bucket);
return APR_ECONNABORTED;
}
if (!inctx->ssl) {
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) {
return APR_ENOTIMPL;
}
inctx->mode = mode;
inctx->block = block;
if ((status = ssl_io_filter_connect(inctx->filter_ctx)) != APR_SUCCESS) {
return ssl_io_filter_error(f, bb, status);
}
if (is_init) {
return APR_SUCCESS;
}
if (inctx->mode == AP_MODE_READBYTES ||
inctx->mode == AP_MODE_SPECULATIVE) {
if (readbytes < len) {
len = (apr_size_t)readbytes;
}
status = ssl_io_input_read(inctx, inctx->buffer, &len);
}
else if (inctx->mode == AP_MODE_GETLINE) {
status = ssl_io_input_getline(inctx, inctx->buffer, &len);
}
else {
status = APR_ENOTIMPL;
}
inctx->block = APR_BLOCK_READ;
if (status != APR_SUCCESS) {
return ssl_io_filter_error(f, bb, status);
}
if (len > 0) {
apr_bucket *bucket =
apr_bucket_transient_create(inctx->buffer, len, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, bucket);
}
return APR_SUCCESS;
}
static apr_status_t ssl_io_filter_output(ap_filter_t *f,
apr_bucket_brigade *bb)
{
apr_status_t status = APR_SUCCESS;
ssl_filter_ctx_t *filter_ctx = f->ctx;
bio_filter_in_ctx_t *inctx;
bio_filter_out_ctx_t *outctx;
apr_read_type_e rblock = APR_NONBLOCK_READ;
if (f->c->aborted) {
apr_brigade_cleanup(bb);
return APR_ECONNABORTED;
}
if (!filter_ctx->pssl) {
return ap_pass_brigade(f->next, bb);
}
inctx = (bio_filter_in_ctx_t *)filter_ctx->pbioRead->ptr;
outctx = (bio_filter_out_ctx_t *)filter_ctx->pbioWrite->ptr;
inctx->mode = AP_MODE_READBYTES;
inctx->block = APR_BLOCK_READ;
if ((status = ssl_io_filter_connect(filter_ctx)) != APR_SUCCESS) {
return ssl_io_filter_error(f, bb, status);
}
while (!APR_BRIGADE_EMPTY(bb)) {
apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) {
status = outctx->rc;
break;
}
if (APR_BUCKET_IS_EOS(bucket)) {
if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
return status;
}
break;
}
else {
apr_bucket_delete(bucket);
}
}
else if (AP_BUCKET_IS_EOC(bucket)) {
filter_ctx->nobuffer = 1;
status = ssl_filter_io_shutdown(filter_ctx, f->c, 0);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, status, f->c,
"SSL filter error shutting down I/O");
}
if ((status = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
return status;
}
break;
}
else {
const char *data;
apr_size_t len;
status = apr_bucket_read(bucket, &data, &len, rblock);
if (APR_STATUS_IS_EAGAIN(status)) {
if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) {
status = outctx->rc;
break;
}
rblock = APR_BLOCK_READ;
continue;
}
rblock = APR_NONBLOCK_READ;
if (!APR_STATUS_IS_EOF(status) && (status != APR_SUCCESS)) {
break;
}
status = ssl_filter_write(f, data, len);
apr_bucket_delete(bucket);
if (status != APR_SUCCESS) {
break;
}
}
}
return status;
}
struct modssl_buffer_ctx {
apr_bucket_brigade *bb;
apr_pool_t *pool;
};
int ssl_io_buffer_fill(request_rec *r, apr_size_t maxlen)
{
conn_rec *c = r->connection;
struct modssl_buffer_ctx *ctx;
apr_bucket_brigade *tempb;
apr_off_t total = 0;
int eos = 0;
ctx = apr_palloc(r->pool, sizeof *ctx);
apr_pool_create(&ctx->pool, r->pool);
ctx->bb = apr_brigade_create(ctx->pool, c->bucket_alloc);
tempb = apr_brigade_create(r->pool, c->bucket_alloc);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "filling buffer, max size "
"%" APR_SIZE_T_FMT " bytes", maxlen);
do {
apr_status_t rv;
apr_bucket *e, *next;
rv = ap_get_brigade(r->proto_input_filters, tempb, AP_MODE_READBYTES,
APR_BLOCK_READ, 8192);
if (rv) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"could not read request body for SSL buffer");
return HTTP_INTERNAL_SERVER_ERROR;
}
for (e = APR_BRIGADE_FIRST(tempb);
e != APR_BRIGADE_SENTINEL(tempb) && !eos; e = next) {
const char *data;
apr_size_t len;
next = APR_BUCKET_NEXT(e);
if (APR_BUCKET_IS_EOS(e)) {
eos = 1;
} else if (!APR_BUCKET_IS_METADATA(e)) {
rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"could not read bucket for SSL buffer");
return HTTP_INTERNAL_SERVER_ERROR;
}
total += len;
}
rv = apr_bucket_setaside(e, ctx->pool);
if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"could not setaside bucket for SSL buffer");
return HTTP_INTERNAL_SERVER_ERROR;
}
APR_BUCKET_REMOVE(e);
APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"total of %" APR_OFF_T_FMT " bytes in buffer, eos=%d",
total, eos);
if (total > maxlen) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"request body exceeds maximum size (%" APR_SIZE_T_FMT
") for SSL buffer", maxlen);
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
} while (!eos);
apr_brigade_destroy(tempb);
while (r->proto_input_filters->frec->ftype < AP_FTYPE_CONNECTION) {
ap_remove_input_filter(r->proto_input_filters);
}
ap_add_input_filter(ssl_io_buffer, ctx, r, c);
return 0;
}
static apr_status_t ssl_io_filter_buffer(ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t bytes)
{
struct modssl_buffer_ctx *ctx = f->ctx;
apr_status_t rv;
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"read from buffered SSL brigade, mode %d, "
"%" APR_OFF_T_FMT " bytes",
mode, bytes);
if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
return APR_ENOTIMPL;
}
if (APR_BRIGADE_EMPTY(ctx->bb)) {
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(f->c->bucket_alloc));
return APR_SUCCESS;
}
if (mode == AP_MODE_READBYTES) {
apr_bucket *e;
rv = apr_brigade_partition(ctx->bb, bytes, &e);
if (rv && rv != APR_INCOMPLETE) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c,
"could not partition buffered SSL brigade");
ap_remove_input_filter(f);
return rv;
}
if (rv == APR_INCOMPLETE) {
APR_BRIGADE_CONCAT(bb, ctx->bb);
} else {
apr_bucket *d = APR_BRIGADE_FIRST(ctx->bb);
e = APR_BUCKET_PREV(e);
APR_RING_UNSPLICE(d, e, link);
APR_RING_SPLICE_HEAD(&bb->list, d, e, apr_bucket, link);
APR_BRIGADE_CHECK_CONSISTENCY(bb);
APR_BRIGADE_CHECK_CONSISTENCY(ctx->bb);
}
}
else {
rv = apr_brigade_split_line(bb, ctx->bb, block, bytes);
if (rv) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c,
"could not split line from buffered SSL brigade");
ap_remove_input_filter(f);
return rv;
}
}
if (APR_BRIGADE_EMPTY(ctx->bb)) {
apr_bucket *e = APR_BRIGADE_LAST(bb);
if (e == APR_BRIGADE_SENTINEL(bb) || !APR_BUCKET_IS_EOS(e)) {
e = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"buffered SSL brigade exhausted");
}
return APR_SUCCESS;
}
static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
SSL *ssl)
{
bio_filter_in_ctx_t *inctx;
inctx = apr_palloc(c->pool, sizeof(*inctx));
filter_ctx->pInputFilter = ap_add_input_filter(ssl_io_filter, inctx, NULL, c);
filter_ctx->pbioRead = BIO_new(&bio_filter_in_method);
filter_ctx->pbioRead->ptr = (void *)inctx;
inctx->ssl = ssl;
inctx->bio_out = filter_ctx->pbioWrite;
inctx->f = filter_ctx->pInputFilter;
inctx->rc = APR_SUCCESS;
inctx->mode = AP_MODE_READBYTES;
inctx->cbuf.length = 0;
inctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
inctx->block = APR_BLOCK_READ;
inctx->pool = c->pool;
inctx->filter_ctx = filter_ctx;
}
void ssl_io_filter_init(conn_rec *c, SSL *ssl)
{
ssl_filter_ctx_t *filter_ctx;
filter_ctx = apr_palloc(c->pool, sizeof(ssl_filter_ctx_t));
filter_ctx->config = myConnConfig(c);
filter_ctx->nobuffer = 0;
filter_ctx->pOutputFilter = ap_add_output_filter(ssl_io_filter,
filter_ctx, NULL, c);
filter_ctx->pbioWrite = BIO_new(&bio_filter_out_method);
filter_ctx->pbioWrite->ptr = (void *)bio_filter_out_ctx_new(filter_ctx, c);
c->clogging_input_filters = 1;
ssl_io_input_add_filter(filter_ctx, c, ssl);
SSL_set_bio(ssl, filter_ctx->pbioRead, filter_ctx->pbioWrite);
filter_ctx->pssl = ssl;
apr_pool_cleanup_register(c->pool, (void*)filter_ctx,
ssl_io_filter_cleanup, apr_pool_cleanup_null);
if (c->base_server->loglevel >= APLOG_DEBUG) {
BIO_set_callback(SSL_get_rbio(ssl), ssl_io_data_cb);
BIO_set_callback_arg(SSL_get_rbio(ssl), (void *)ssl);
}
return;
}
void ssl_io_filter_register(apr_pool_t *p)
{
ap_register_output_filter ("UPGRADE_FILTER", ssl_io_filter_Upgrade, NULL, AP_FTYPE_PROTOCOL + 5);
ap_register_input_filter (ssl_io_filter, ssl_io_filter_input, NULL, AP_FTYPE_CONNECTION + 5);
ap_register_output_filter (ssl_io_filter, ssl_io_filter_output, NULL, AP_FTYPE_CONNECTION + 5);
ap_register_input_filter (ssl_io_buffer, ssl_io_filter_buffer, NULL, AP_FTYPE_PROTOCOL);
return;
}
#define DUMP_WIDTH 16
static void ssl_io_data_dump(server_rec *srvr,
MODSSL_BIO_CB_ARG_TYPE *s,
long len)
{
char buf[256];
char tmp[64];
int i, j, rows, trunc;
unsigned char ch;
trunc = 0;
for(; (len > 0) && ((s[len-1] == ' ') || (s[len-1] == '\0')); len--)
trunc++;
rows = (len / DUMP_WIDTH);
if ((rows * DUMP_WIDTH) < len)
rows++;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
"+-------------------------------------------------------------------------+");
for(i = 0 ; i< rows; i++) {
#if APR_CHARSET_EBCDIC
char ebcdic_text[DUMP_WIDTH];
j = DUMP_WIDTH;
if ((i * DUMP_WIDTH + j) > len)
j = len % DUMP_WIDTH;
if (j == 0) j = DUMP_WIDTH;
memcpy(ebcdic_text, (char *)(s) + i * DUMP_WIDTH, j);
ap_xlate_proto_from_ascii(ebcdic_text, j);
#endif
apr_snprintf(tmp, sizeof(tmp), "| %04x: ", i * DUMP_WIDTH);
apr_cpystrn(buf, tmp, sizeof(buf));
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len)
apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
else {
ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
apr_snprintf(tmp, sizeof(tmp), "%02x%c", ch , j==7 ? '-' : ' ');
apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
}
}
apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
for (j = 0; j < DUMP_WIDTH; j++) {
if (((i * DUMP_WIDTH) + j) >= len)
apr_cpystrn(buf+strlen(buf), " ", sizeof(buf)-strlen(buf));
else {
ch = ((unsigned char)*((char *)(s) + i * DUMP_WIDTH + j)) & 0xff;
#if APR_CHARSET_EBCDIC
apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= 0x20 ) && (ch <= 0x7e )) ? ebcdic_text[j] : '.');
#else
apr_snprintf(tmp, sizeof(tmp), "%c", ((ch >= ' ') && (ch <= '~')) ? ch : '.');
#endif
apr_cpystrn(buf+strlen(buf), tmp, sizeof(buf)-strlen(buf));
}
}
apr_cpystrn(buf+strlen(buf), " |", sizeof(buf)-strlen(buf));
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
"%s", buf);
}
if (trunc > 0)
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
"| %04ld - <SPACES/NULS>", len + trunc);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, srvr,
"+-------------------------------------------------------------------------+");
return;
}
long ssl_io_data_cb(BIO *bio, int cmd,
MODSSL_BIO_CB_ARG_TYPE *argp,
int argi, long argl, long rc)
{
SSL *ssl;
conn_rec *c;
server_rec *s;
if ((ssl = (SSL *)BIO_get_callback_arg(bio)) == NULL)
return rc;
if ((c = (conn_rec *)SSL_get_app_data(ssl)) == NULL)
return rc;
s = mySrvFromConn(c);
if ( cmd == (BIO_CB_WRITE|BIO_CB_RETURN)
|| cmd == (BIO_CB_READ |BIO_CB_RETURN) ) {
if (rc >= 0) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"%s: %s %ld/%d bytes %s BIO#%pp [mem: %pp] %s",
SSL_LIBRARY_NAME,
(cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
rc, argi, (cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "to" : "from"),
bio, argp,
(argp != NULL ? "(BIO dump follows)" : "(Oops, no memory buffer?)"));
if (argp != NULL)
ssl_io_data_dump(s, argp, rc);
}
else {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"%s: I/O error, %d bytes expected to %s on BIO#%pp [mem: %pp]",
SSL_LIBRARY_NAME, argi,
(cmd == (BIO_CB_WRITE|BIO_CB_RETURN) ? "write" : "read"),
bio, argp);
}
}
return rc;
}