/* Licensed to the Apache Software Foundation (ASF) under one or more *contributorlicenseagreements.SeetheNOTICEfiledistributedwith *thisworkforadditionalinformationregardingcopyrightownership. *TheASFlicensesthisfiletoYouundertheApacheLicense,Version2.0 *(the"License");youmaynotusethisfileexceptincompliancewith *theLicense.YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
/* This file is designed to be the bridge between OpenSSL and httpd. *However,wereallydon'texpectanyone(letaloneourselves)to *rememberwhatisinthisfile.So,first,aquickoverview. * *Inthisfile,youwillfind: *-ssl_io_filter_input(Apacheinputfilter) *-ssl_io_filter_output(Apacheoutputfilter) * *-bio_filter_in_*(OpenSSLinputfilter) *-bio_filter_out_*(OpenSSLoutputfilter) * *Theinputchainisroughly: * *ssl_io_filter_input->ssl_io_input_read->SSL_read->... *...->bio_filter_in_read->ap_get_brigade/next-httpd-filter * *Inmortalterminology,wedothefollowing: *-ReceivearequestfordatatotheSSLinputfilter *-Callahelperfunctiononceweknowweshouldperformaread *-CallOpenSSL'sSSL_read() *-SSL_read()willthencallbio_filter_in_read *-bio_filter_in_readwillthentrytofetchdatafromthenexthttpdfilter *-bio_filter_in_readwillflattenthatdataandreturnittoSSL_read *-SSL_readwillthendecryptthedata *-ssl_io_input_readwillthenreceivedecrypteddataasachar*and *ensurethattherewerenoreaderrors *-Thechar*isplacedinabrigadeandreturned * *Sinceconnection-levelinputfiltersinhttpdneedtobeableto *handleAP_MODE_GETLINEcalls(namelyidentifyingLF-terminatedstrings), *ssl_io_input_getlinewhichwillhandlethisspecialcase. * *DuetoAP_MODE_GETLINEandAP_MODE_SPECULATIVE,wemaysometimeshave *'leftover'decodeddatawhichmustbesetasideforthenextread.That *iscurrentlyhandledbythechar_buffer_{read|write}functions.So, *ssl_io_input_readmaybeabletofulfillreadswithoutinvoking *SSL_read(). * *Notethatthefiltercontextofssl_io_filter_inputandbio_filter_in_* *aresharedasbio_filter_in_ctx_t. * *Notethatthefilterisbychoicelimitedtoreadingatmost *AP_IOBUFSIZE(8192bytes)percall. *
*/
/* this custom BIO allows us to hook SSL_write directly into *anapr_bucket_brigadeandusetransientbucketswiththeSSL *malloc-edbuffer,ratherthancopyingintoamemBIO. *alsoallowsustopassthebrigadeasdataisbeingwritten *ratherthanbufferinguptheentireresponseinthememBIO. * *whenSSLneedstoflush(e.g.SSL_accept()),itwillcallBIO_flush() *whichwilltriggeracalltobio_filter_out_ctrl()->bio_filter_out_flush(). *soweonlyneedtoflushtheoutputourselvesifwereceivean *EOSorFLUSHbucket.thiswasnotpossiblewiththememBIOwherewe *hadtoflushallovertheplacenotreallyknowingwhenitwasrequired *todoso.
*/
typedefstruct {
SSL *pssl;
BIO *pbioRead;
BIO *pbioWrite;
ap_filter_t *pInputFilter;
ap_filter_t *pOutputFilter;
SSLConnRec *config;
} ssl_filter_ctx_t;
typedefstruct {
ssl_filter_ctx_t *filter_ctx;
conn_rec *c;
apr_bucket_brigade *bb; /* Brigade used as a buffer. */
apr_status_t rc;
} bio_filter_out_ctx_t;
/* Pass an output brigade down the filter stack; returns 1 on success
* or -1 on failure. */ staticint bio_filter_out_pass(bio_filter_out_ctx_t *outctx)
{
AP_DEBUG_ASSERT(!APR_BRIGADE_EMPTY(outctx->bb));
outctx->rc = ap_pass_brigade(outctx->filter_ctx->pOutputFilter->next,
outctx->bb); /* Fail if the connection was reset: */ if (outctx->rc == APR_SUCCESS && outctx->c->aborted) {
outctx->rc = APR_ECONNRESET;
} return (outctx->rc == APR_SUCCESS) ? 1 : -1;
}
/* Send a FLUSH bucket down the output filter stack; returns 1 on
* success, -1 on failure. */ staticint bio_filter_out_flush(BIO *bio)
{
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
apr_bucket *e;
/* nothing to free here. *apachewilldestroythebucketbrigadeforus
*/ return1;
}
staticint bio_filter_out_read(BIO *bio, char *out, int outl)
{ /* this is never called */
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, "BUG: %s() should not be called", "bio_filter_out_read");
AP_DEBUG_ASSERT(0); return -1;
}
staticint bio_filter_out_write(BIO *bio, constchar *in, int inl)
{
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
apr_bucket *e; int need_flush;
BIO_clear_retry_flags(bio);
#ifndef SSL_OP_NO_RENEGOTIATION /* Abort early if the client has initiated a renegotiation. */ if (outctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
outctx->rc = APR_ECONNABORTED; return -1;
} #endif
/* Use a transient bucket for the output data - any downstream
* filter must setaside if necessary. */
e = apr_bucket_transient_create(in, inl, outctx->bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
/* In theory, OpenSSL should flush as necessary, but it is known *nottodosocorrectlyinsomecases(<0.9.8m;seePR46952), *orontheproxy/clientside(afterssl23_client_hello(),e.g. *ssl/proxy.ttestsuite). * *Historically,thisflushcallwasperformedonlyforanSSLv2 *connectionorforaproxyconnection.Calling_out_flushcan *beexpensiveincaseswhererequests/responsesarepipelined, *solimittheperformanceimpacttohandshaketime.
*/ #if OPENSSL_VERSION_NUMBER < 0x0009080df
need_flush = !SSL_is_init_finished(outctx->filter_ctx->pssl); #else
need_flush = SSL_in_connect_init(outctx->filter_ctx->pssl); #endif if (need_flush) {
e = apr_bucket_flush_create(outctx->bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(outctx->bb, e);
}
if (bio_filter_out_pass(outctx) < 0) { return -1;
}
return inl;
}
staticlong bio_filter_out_ctrl(BIO *bio, int cmd, long num, void *ptr)
{ long ret = 1;
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
switch (cmd) { case BIO_CTRL_RESET: case BIO_CTRL_EOF: case BIO_C_SET_BUF_MEM_EOF_RETURN:
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, outctx->c, "output bio: unhandled control %d", cmd);
ret = 0; break; case BIO_CTRL_WPENDING: case BIO_CTRL_PENDING: case BIO_CTRL_INFO:
ret = 0; break; case BIO_CTRL_GET_CLOSE:
ret = (long)BIO_get_shutdown(bio); break; case BIO_CTRL_SET_CLOSE:
BIO_set_shutdown(bio, (int)num); break; case BIO_CTRL_FLUSH:
ret = bio_filter_out_flush(bio); break; case BIO_CTRL_DUP:
ret = 1; break; /* N/A */ case BIO_C_SET_BUF_MEM: case BIO_C_GET_BUF_MEM_PTR: /* we don't care */ case BIO_CTRL_PUSH: case BIO_CTRL_POP: default:
ret = 0; break;
}
return ret;
}
staticint bio_filter_out_gets(BIO *bio, char *buf, int size)
{ /* this is never called */
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, "BUG: %s() should not be called", "bio_filter_out_gets");
AP_DEBUG_ASSERT(0); return -1;
}
staticint bio_filter_out_puts(BIO *bio, constchar *str)
{ /* this is never called */
bio_filter_out_ctx_t *outctx = (bio_filter_out_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, outctx->c, "BUG: %s() should not be called", "bio_filter_out_puts");
AP_DEBUG_ASSERT(0); return -1;
}
typedefstruct { int length; char *value;
} char_buffer_t;
/* Copy up to INL bytes from the char_buffer BUFFER into IN. Note *thatduetothestrangewaythisAPIisdesigned/used,the *char_bufferobjectisusedtocacheasegmentofinctx->buffer,and *thenthisfunctioncalledtocopy(partof)thatsegmenttothe *beginningofinctx->buffer.Sothesegmentstocopycannotbe
* presumed to be non-overlapping, and memmove must be used. */ staticint char_buffer_read(char_buffer_t *buffer, char *in, int inl)
{ if (!buffer->length) { return0;
}
if (buffer->length > inl) { /* we have enough to fill the caller's buffer */
memmove(in, buffer->value, inl);
buffer->value += inl;
buffer->length -= inl;
} else { /* swallow remainder of the buffer */
memmove(in, buffer->value, buffer->length);
inl = buffer->length;
buffer->value = NULL;
buffer->length = 0;
}
/* This function will read from a brigade and discard the read buckets as it *proceeds.Itwillreadatmost*lenbytes.
*/ 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;
/* Justin points out this is an http-ism that might *notfitifbrigade_consumeisaddedtoAPR.Perhaps *apr_bucket_read(eos_bucket)shouldreturnAPR_EOF? *Thenthisbecomesmainlineinsteadofaone-off.
*/ if (APR_BUCKET_IS_EOS(b)) {
status = APR_EOF; break;
}
/* The reason I'm not offering brigade_consume yet *acrosstoapr-utilisthatthefollowingcall *illustrateshowborkedthatAPIreallyis.For *thissortofcase(callerprovidedbuffer)it *wouldbemuchmoretrivialforapr_bucket_consume *todoalltheworkthatfollows,basedonthe *particularcharacteristicsofthebucketweare *consuminghere.
*/
status = apr_bucket_read(b, &str, &str_len, block);
if (status != APR_SUCCESS) { if (APR_STATUS_IS_EOF(status)) { /* This stream bucket was consumed */
apr_bucket_delete(b); continue;
} break;
}
if (str_len > 0) { /* Do not block once some data has been consumed */
block = APR_NONBLOCK_READ;
/* Assure we don't overflow. */
consume = (str_len + actual > *len) ? *len - actual : str_len;
memcpy(c, str, consume);
c += consume;
actual += consume;
if (consume >= b->length) { /* This physical bucket was consumed */
apr_bucket_delete(b);
} else { /* Only part of this physical bucket was consumed */
b->start += consume;
b->length -= consume;
}
} elseif (b->length == 0) {
apr_bucket_delete(b);
}
/* This could probably be actual == *len, but be safe from stray
* photons. */ if (actual >= *len) { break;
}
}
/* OpenSSL catches this case, so should we. */ if (!in) return0;
BIO_clear_retry_flags(bio);
#ifndef SSL_OP_NO_RENEGOTIATION /* Abort early if the client has initiated a renegotiation. */ if (inctx->filter_ctx->config->reneg_state == RENEG_ABORT) {
inctx->rc = APR_ECONNABORTED; return -1;
} #endif
if (!inctx->bb) {
inctx->rc = APR_EOF; return -1;
}
/* If the read returns EAGAIN or success with an empty *brigade,returnanerroraftersettingtheretryflag; *SSL_read()willthenreturn-1,andSSL_get_error()will
* indicate SSL_ERROR_WANT_READ. */ 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 (block == APR_BLOCK_READ
&& APR_STATUS_IS_TIMEUP(inctx->rc)
&& APR_BRIGADE_EMPTY(inctx->bb)) { /* don't give up, just return the timeout */ return -1;
} if (inctx->rc != APR_SUCCESS) { /* Unexpected errors discard the brigade */
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;
}
/* Unexpected errors and APR_EOF clean out the brigade. *SubsequentcallswillreturnAPR_EOF.
*/
apr_brigade_cleanup(inctx->bb);
inctx->bb = NULL;
if (APR_STATUS_IS_EOF(inctx->rc) && inl) { /* Provide the results of this read pass, *withoutresettingtheBIOretry_readflag
*/ return (int)inl;
}
return -1;
}
staticint bio_filter_in_write(BIO *bio, constchar *in, int inl)
{
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, "BUG: %s() should not be called", "bio_filter_in_write");
AP_DEBUG_ASSERT(0); return -1;
}
staticint bio_filter_in_puts(BIO *bio, constchar *str)
{
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, "BUG: %s() should not be called", "bio_filter_in_puts");
AP_DEBUG_ASSERT(0); return -1;
}
staticint bio_filter_in_gets(BIO *bio, char *buf, int size)
{
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, "BUG: %s() should not be called", "bio_filter_in_gets");
AP_DEBUG_ASSERT(0); return -1;
}
staticlong bio_filter_in_ctrl(BIO *bio, int cmd, long num, void *ptr)
{
bio_filter_in_ctx_t *inctx = (bio_filter_in_ctx_t *)BIO_get_data(bio); switch (cmd) { #ifdef BIO_CTRL_EOF case BIO_CTRL_EOF: return inctx->rc == APR_EOF; #endif default: break;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, inctx->f->c, "BUG: bio_filter_in_ctrl() should not be called with cmd=%i",
cmd); return0;
}
#if MODSSL_USE_OPENSSL_PRE_1_1_API
static BIO_METHOD bio_filter_out_method = {
BIO_TYPE_MEM, "APR output filter",
bio_filter_out_write,
bio_filter_out_read, /* read is never called */
bio_filter_out_puts, /* puts is never called */
bio_filter_out_gets, /* gets is never called */
bio_filter_out_ctrl,
bio_filter_create,
bio_filter_destroy,
NULL
};
static BIO_METHOD bio_filter_in_method = {
BIO_TYPE_MEM, "APR input filter",
bio_filter_in_write, /* write is never called */
bio_filter_in_read,
bio_filter_in_puts, /* puts is never called */
bio_filter_in_gets, /* gets is never called */
bio_filter_in_ctrl, /* ctrl is called for EOF check */
bio_filter_create,
bio_filter_destroy,
NULL
};
void init_bio_methods(void)
{
bio_filter_out_method = BIO_meth_new(BIO_TYPE_MEM, "APR output filter");
BIO_meth_set_write(bio_filter_out_method, &bio_filter_out_write);
BIO_meth_set_read(bio_filter_out_method, &bio_filter_out_read); /* read is never called */
BIO_meth_set_puts(bio_filter_out_method, &bio_filter_out_puts); /* puts is never called */
BIO_meth_set_gets(bio_filter_out_method, &bio_filter_out_gets); /* gets is never called */
BIO_meth_set_ctrl(bio_filter_out_method, &bio_filter_out_ctrl);
BIO_meth_set_create(bio_filter_out_method, &bio_filter_create);
BIO_meth_set_destroy(bio_filter_out_method, &bio_filter_destroy);
bio_filter_in_method = BIO_meth_new(BIO_TYPE_MEM, "APR input filter");
BIO_meth_set_write(bio_filter_in_method, &bio_filter_in_write); /* write is never called */
BIO_meth_set_read(bio_filter_in_method, &bio_filter_in_read);
BIO_meth_set_puts(bio_filter_in_method, &bio_filter_in_puts); /* puts is never called */
BIO_meth_set_gets(bio_filter_in_method, &bio_filter_in_gets); /* gets is never called */
BIO_meth_set_ctrl(bio_filter_in_method, &bio_filter_in_ctrl); /* ctrl is never called */
BIO_meth_set_create(bio_filter_in_method, &bio_filter_create);
BIO_meth_set_destroy(bio_filter_in_method, &bio_filter_destroy);
}
/* If we have something leftover from last time, try that first. */ if ((bytes = char_buffer_read(&inctx->cbuf, buf, wanted))) {
*len = bytes; if (inctx->mode == AP_MODE_SPECULATIVE) { /* We want to rollback this read. */ 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;
} /* This could probably be *len == wanted, but be safe from stray *photons.
*/ if (*len >= wanted) { return APR_SUCCESS;
} if (inctx->mode == AP_MODE_GETLINE) { if (memchr(buf, APR_ASCII_LF, *len)) { return APR_SUCCESS;
}
} else { /* Down to a nonblock pattern as we have some data already
*/
inctx->block = APR_NONBLOCK_READ;
}
}
while (1) {
if (!inctx->filter_ctx->pssl) { /* Ensure a non-zero error code is returned */ if (inctx->rc == APR_SUCCESS) {
inctx->rc = APR_EGENERAL;
} break;
}
/* We rely on SSL_get_error() after the read, which requires an empty *errorqueuebeforethereadinordertoworkproperly.
*/
ERR_clear_error();
/* SSL_read may not read because we haven't taken enough data *fromthestack.Thisiswherewewanttoconsiderallof *theblockingandSPECULATIVEsemantics
*/
rc = SSL_read(inctx->filter_ctx->pssl, buf + bytes, wanted - bytes);
if (rc > 0) {
*len += rc; if (inctx->mode == AP_MODE_SPECULATIVE) { /* We want to rollback this read. */
char_buffer_write(&inctx->cbuf, buf, rc);
} return inctx->rc;
} else/* (rc <= 0) */ { int ssl_err;
conn_rec *c; if (rc == 0) { /* If EAGAIN, we will loop given a blocking read, *otherwiseconsiderourselvesatEOF.
*/ if (APR_STATUS_IS_EAGAIN(inctx->rc)
|| APR_STATUS_IS_EINTR(inctx->rc)) { /* Already read something, return APR_SUCCESS instead. *Onwin32inparticular,butperhapsonotherkernels, *ablockingcallisn't'always'blocking.
*/ if (*len > 0) {
inctx->rc = APR_SUCCESS; break;
} if (inctx->block == APR_NONBLOCK_READ) { break;
}
} else { if (*len > 0) {
inctx->rc = APR_SUCCESS; break;
}
}
}
ssl_err = SSL_get_error(inctx->filter_ctx->pssl, rc);
c = (conn_rec*)SSL_get_app_data(inctx->filter_ctx->pssl);
while (tmplen > 0) {
status = ssl_io_input_read(inctx, buf + offset, &tmplen);
if (status != APR_SUCCESS) { if (APR_STATUS_IS_EAGAIN(status) && (*len > 0)) { /* Save the part of the line we already got */
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;
/* XXX: probably a better way to determine this */ if (SSL_total_renegotiations(filter_ctx->pssl)) {
reason = "likely due to failed renegotiation";
}
ap_log_cerror(APLOG_MARK, APLOG_INFO, outctx->rc, c, APLOGNO(01995) "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;
}
/* Just use a simple request. Any request will work for this, because *weuseaflagintheconn_rec->conn_vectornow.Thefakerequestjust *getstherequestbacktotheApachecoresothataresponsecanbesent. *SinceweuseanHTTP/1.xrequest,wealsohavetoinjecttheemptyline *thatterminatestheheaders,orthecorewillreadmoredatafromthe *socket.
*/ #define HTTP_ON_HTTPS_PORT \ "GET / HTTP/1.0" CRLF
/* Custom apr_status_t error code, used when a plain HTTP request is
* received on an SSL port. */ #define MODSSL_ERROR_HTTP_ON_HTTPS (APR_OS_START_USERERR + 0)
/* Custom apr_status_t error code, used when the proxy cannot
* establish an outgoing SSL connection. */ #define MODSSL_ERROR_BAD_GATEWAY (APR_OS_START_USERERR + 1)
/* *exchangeclosenotifymessages,butallowtheuser *toforcethetypeofhandshakeviaSetEnvIfdirective
*/ if (abortive) {
shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
type = "abortive";
logno = APLOGNO(01998);
loglevel = APLOG_INFO;
} elseswitch (sslconn->shutdown_type) { case SSL_SHUTDOWN_TYPE_UNCLEAN: /* perform no close notify handshake at all
(violates the SSL/TLS standard!) */
shutdown_type = SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN;
type = "unclean";
logno = APLOGNO(01999); break; case SSL_SHUTDOWN_TYPE_ACCURATE: /* send close notify and wait for clients close notify
(standard compliant, but usually causes connection hangs) */
shutdown_type = 0;
type = "accurate";
logno = APLOGNO(02000); break; default: /* *caseSSL_SHUTDOWN_TYPE_UNSET: *caseSSL_SHUTDOWN_TYPE_STANDARD:
*/ /* send close notify, but don't wait for clients close notify
(standard compliant and safe, so it's the DEFAULT!) */
shutdown_type = SSL_RECEIVED_SHUTDOWN;
type = "standard";
logno = APLOGNO(02001); break;
}
/* Perform the SSL handshake (whether in client or server mode), if
* necessary, for the given connection. */ static apr_status_t ssl_io_filter_handshake(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 (c->outgoing) { #ifdef HAVE_TLSEXT
apr_ipsubnet_t *ip; #ifdef HAVE_TLS_ALPN constchar *alpn_note;
apr_array_header_t *alpn_proposed = NULL; int alpn_empty_ok = 1; #endif #endif constchar *hostname_note = apr_table_get(c->notes, "proxy-request-hostname"); BOOL proxy_ssl_check_peer_ok = TRUE; int post_handshake_rc = OK;
SSLDirConfigRec *dc;
/* Do string match or simplest wildcard match if that
* fails. */
match = strcasecmp(hostname, hostname_note) == 0; if (!match && strncmp(hostname, "*.", 2) == 0) { constchar *p = ap_strchr_c(hostname_note, '.');
#ifdef HAVE_TLS_ALPN /* If we proposed ALPN protocol(s), we need to check if the server *agreedtooneofthem.While<https://www.rfc-editor.org/rfc/rfc7301.txt> *chapter3.2saystheserverSHALLerrorthehandshakeinsuchacase, *therealityisthatsomeserversfallbacktotheirdefault,e.g.http/1.1. *(wealsodothisrightnow) *Weneedtotreatthisasanerrorforsecurityreasons.
*/ if (alpn_proposed && alpn_proposed->nelts > 0) { constchar *selected; unsignedint slen;
SSL_get0_alpn_selected(filter_ctx->pssl, (constunsignedchar**)&selected, &slen); if (!selected || !slen) { /* No ALPN selection reported by the remote server. This could mean *itdoesnotsupportALPN(oldserver)orthatitdoesnotsupport
* any of our proposals (Apache itself up to 2.4.48 at least did that). */ if (!alpn_empty_ok) {
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10273) "SSL Proxy: Peer did not select any of our ALPN protocols [%s].",
alpn_note);
proxy_ssl_check_peer_ok = FALSE;
}
} else { constchar *proto; int i, found = 0; for (i = 0; !found && i < alpn_proposed->nelts; ++i) {
proto = APR_ARRAY_IDX(alpn_proposed, i, constchar *);
found = !strncmp(selected, proto, slen);
} if (!found) { /* From a conforming peer, this should never happen,
* but life always finds a way... */
proto = apr_pstrndup(c->pool, selected, slen);
ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(10274) "SSL Proxy: Peer proposed ALPN protocol %s which is none " "of our proposals [%s].", proto, alpn_note);
proxy_ssl_check_peer_ok = FALSE;
}
}
} #endif
if (proxy_ssl_check_peer_ok == TRUE) { /* another chance to fail */
post_handshake_rc = ssl_run_proxy_post_handshake(c, filter_ctx->pssl);
}
if (cert) {
X509_free(cert);
}
if (proxy_ssl_check_peer_ok != TRUE
|| (post_handshake_rc != OK && post_handshake_rc != DECLINED)) { /* ensure that the SSL structures etc are freed, etc: */
ssl_filter_io_shutdown(filter_ctx, c, 1);
apr_table_setn(c->notes, "SSL_connect_rv", "err"); return MODSSL_ERROR_BAD_GATEWAY;
}
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; constchar *start = inctx->buffer; /* start of block to return */
apr_size_t len = sizeof(inctx->buffer); /* length of block to return */ int is_init = (mode == AP_MODE_INIT);
apr_bucket *bucket;
if (f->c->aborted) { /* XXX: Ok, if we aborted, we ARE at the EOS. We also have *aborted.This'doubleprotection'isprobablyredundant, *butalsoeffectiveagainstjustaboutanything.
*/
bucket = apr_bucket_eos_create(f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, bucket); return APR_ECONNABORTED;
}
/* XXX: we don't currently support anything other than these modes. */ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE &&
mode != AP_MODE_SPECULATIVE && mode != AP_MODE_INIT) { return APR_ENOTIMPL;
}
inctx->mode = mode;
inctx->block = block;
/* XXX: we could actually move ssl_io_filter_handshake to an *ap_hook_process_connectionbutwouldstillneedtocallitfor *AP_MODE_INITforprotocolsthatmayupgradetheconnection *ratherthanhaveSSLEngineOnconfigured.
*/ if ((status = ssl_io_filter_handshake(inctx->filter_ctx)) != APR_SUCCESS) { return ssl_io_filter_error(inctx, bb, status, is_init);
}
if (is_init) { /* protocol module needs to handshake before sending *datatoclient(e.g.NNTPorFTP)
*/ return APR_SUCCESS;
}
if (inctx->mode == AP_MODE_READBYTES ||
inctx->mode == AP_MODE_SPECULATIVE) { /* Protected from truncation, readbytes < MAX_SIZE_T
* FIXME: No, it's *not* protected. -- jre */ if (readbytes < len) {
len = (apr_size_t)readbytes;
}
status = ssl_io_input_read(inctx, inctx->buffer, &len);
} elseif (inctx->mode == AP_MODE_GETLINE) { constchar *pos;
/* Satisfy the read directly out of the buffer if possible; *invokingssl_io_input_getlinewillmeantheentirebuffer
* is copied once (unnecessarily) for each GETLINE call. */ if (inctx->cbuf.length
&& (pos = memchr(inctx->cbuf.value, APR_ASCII_LF,
inctx->cbuf.length)) != NULL) {
start = inctx->cbuf.value;
len = 1 + pos - start; /* +1 to include LF */ /* Buffer contents now consumed. */
inctx->cbuf.value += len;
inctx->cbuf.length -= len;
status = APR_SUCCESS;
} else { /* Otherwise fall back to the hard way. */
status = ssl_io_input_getline(inctx, inctx->buffer, &len);
}
} else { /* We have no idea what you are talking about, so return an error. */
status = APR_ENOTIMPL;
}
/* It is possible for mod_ssl's BIO to be used outside of the *directcontrolofmod_ssl'sinputoroutputfilter--notably, *whenmod_sslinitiatesarenegotiation.SwitchingtheBIOmode *backto"blocking"hereensuressuchoperationsdon'tfailwith
* SSL_ERROR_WANT_READ. */
inctx->block = APR_BLOCK_READ;
/* Create a transient bucket out of the decrypted data. */ if (len > 0) {
bucket =
apr_bucket_transient_create(start, len, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, bucket);
}
/* The brigade consists of zero-or-more small data buckets which *canbecoalesced(referredtoasthe"prefix"),followedbythe *remainderofthebrigade. * *Findthelastbucket-ifany-ofthatprefix.countgives *thenumberofbucketsintheprefix.The"prefix"mustcontain *onlydatabucketswithknownlength,andmustbeofatotal *sizewhichfitsintothebuffer. * *N.B.:Theprocessherecouldberepeatedthroughoutthebrigade *(coalesceanyrunofconsecutivedatabuckets)butthiswould *addsignificantcomplexity,particularlytomemory
* management. */ for (e = APR_BRIGADE_FIRST(bb);
e != APR_BRIGADE_SENTINEL(bb)
&& !APR_BUCKET_IS_METADATA(e)
&& e->length != (apr_size_t)-1
&& e->length <= COALESCE_BYTES
&& (buffered + bytes + e->length) <= COALESCE_BYTES;
e = APR_BUCKET_NEXT(e)) { /* don't count zero-length buckets */ if (e->length) {
bytes += e->length;
count++;
}
}
/* If there is room remaining and the next bucket is a data *bucket,trytoincludeitintheprefixtocoalesce.Fora *typical[HEAP][FILE]HTTPresponsebrigade,thishandles *mergingtheheadersandthestartofthebodyintoasingleTLS
* record. */ if (bytes + buffered > 0
&& bytes + buffered < COALESCE_BYTES
&& e != APR_BRIGADE_SENTINEL(bb)
&& !APR_BUCKET_IS_METADATA(e)) {
apr_status_t rv = APR_SUCCESS;
/* For an indeterminate length bucket (PIPE/CGI/...), try a *non-blockingreadtohaveitmorphintoaHEAP.Ifthe *readfailswithEAGAIN,itisharmlesstotryasplit
* anyway, split is ENOTIMPL for most PIPE-like buckets. */ if (e->length == (apr_size_t)-1) { constchar *discard;
apr_size_t ignore;
rv = apr_bucket_read(e, &discard, &ignore, APR_NONBLOCK_READ); if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10232) "coalesce failed to read from %s bucket",
e->type->name); return AP_FILTER_ERROR;
}
}
if (rv == APR_SUCCESS) { /* If the read above made the bucket morph, it may now fit *entirelywithinthebuffer.Otherwise,splititsoitdoes
* fit. */ if (e->length > COALESCE_BYTES
|| e->length + buffered + bytes > COALESCE_BYTES) {
rv = apr_bucket_split(e, COALESCE_BYTES - (buffered + bytes));
}
if (rv == APR_SUCCESS && e->length == 0) { /* As above, don't count in the prefix if the bucket is
* now zero-length. */
} elseif (rv == APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, "coalesce: adding %" APR_SIZE_T_FMT " bytes " "from split %s bucket, total %" APR_SIZE_T_FMT,
e->length, e->type->name, bytes + buffered);
count++;
bytes += e->length;
e = APR_BUCKET_NEXT(e);
} elseif (rv != APR_ENOTIMPL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(10233) "coalesce: failed to split data bucket"); return AP_FILTER_ERROR;
}
}
}
/* The prefix is zero or more buckets. upto now points to the *bucketAFTERtheendoftheprefix,whichmaybethebrigade
* sentinel. */
upto = e;
/* Coalesce the prefix, if any of the following are true: * *a)theprefixismorethanonebucket *OR *b)theprefixistheentirebrigade,whichisasinglebucket *ANDtheprefixlengthissmallerthanthebuffersize, *OR *c)theprefixisasinglebucket *ANDthereisbuffereddatafromapreviouspass. * *Theaimwith(b)istobufferasmallbucketsoitcanbe *coalescedwithfutureinvocationsofthisfilter.e.g.three *callseachwithasingle100byteHEAPbucketshouldget *coalescedtogether.Butaninvocationwitha8192byteHEAP *shouldpassthroughuntouched.
*/ if (bytes > 0
&& (count > 1
|| (upto == APR_BRIGADE_SENTINEL(bb)
&& bytes < COALESCE_BYTES)
|| (ctx && ctx->bytes > 0))) { /* If coalescing some bytes, ensure a context has been
* created. */ if (!ctx) {
f->ctx = ctx = apr_palloc(f->c->pool, sizeof *ctx);
ctx->bytes = 0;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, "coalesce: have %" APR_SIZE_T_FMT " bytes, " "adding %" APR_SIZE_T_FMT " more (buckets=%u)",
ctx->bytes, bytes, count);
/* Iterate through the prefix segment. For non-fatal errors *inthisloopitissafetobreakoutandfallbacktothe *normalpathofsendingthebuffer+remainingbucketsin
* brigade. */
e = APR_BRIGADE_FIRST(bb); while (e != upto) {
apr_size_t len; constchar *data;
apr_bucket *next;
if (APR_BUCKET_IS_METADATA(e)
|| e->length == (apr_size_t)-1) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02012) "unexpected %s bucket during coalesce",
e->type->name); break; /* non-fatal error; break out */
}
if (e->length) {
apr_status_t rv;
/* A blocking read should be fine here for a *known-lengthdatabucket,ratherthantheusual
* non-block/flush/block. */
rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ); if (rv) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02013) "coalesce failed to read from data bucket"); return AP_FILTER_ERROR;
}
/* Be paranoid. */ if (len > sizeof ctx->buffer
|| (len + ctx->bytes > sizeof ctx->buffer)) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(02014) "unexpected coalesced bucket data length"); break; /* non-fatal error; break out */
}
next = APR_BUCKET_NEXT(e);
apr_bucket_delete(e);
e = next;
}
}
if (APR_BRIGADE_EMPTY(bb)) { /* If the brigade is now empty, our work here is done. */ return APR_SUCCESS;
}
/* If anything remains in the brigade, it must now be passed down *thefilterstack,firstprependinganythingthathasbeen
* coalesced. */ if (ctx && ctx->bytes) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, f->c, "coalesce: passing on %" APR_SIZE_T_FMT " bytes", ctx->bytes);
e = apr_bucket_transient_create(ctx->buffer, ctx->bytes, bb->bucket_alloc);
APR_BRIGADE_INSERT_HEAD(bb, e);
ctx->bytes = 0; /* buffer now emptied. */
}
/* When we are the writer, we must initialize the inctx *modesothatweblockforanyrequiredsslinput,because *outputfilteringisalwaysnonblocking.
*/
inctx->mode = AP_MODE_READBYTES;
inctx->block = APR_BLOCK_READ;
while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
if (APR_BUCKET_IS_METADATA(bucket)) { /* Pass through metadata buckets untouched. EOC is
* special; terminate the SSL layer first. */ if (AP_BUCKET_IS_EOC(bucket)) {
ssl_filter_io_shutdown(filter_ctx, f->c, 0);
}
AP_DEBUG_ASSERT(APR_BRIGADE_EMPTY(outctx->bb));
/* Metadata buckets are passed one per brigade; it might *bemoreefficient(butalsomorecomplex)touse *outctx->bbasatruebufferandinterleavethesewith
* data buckets. */
APR_BUCKET_REMOVE(bucket);
APR_BRIGADE_INSERT_HEAD(outctx->bb, bucket);
status = ap_pass_brigade(f->next, outctx->bb); if (status == APR_SUCCESS && f->c->aborted)
status = APR_ECONNRESET;
apr_brigade_cleanup(outctx->bb);
} else { /* Filter a data bucket. */ constchar *data;
apr_size_t len;
status = apr_bucket_read(bucket, &data, &len, rblock);
if (APR_STATUS_IS_EAGAIN(status)) { /* No data available: flush... */ if (bio_filter_out_flush(filter_ctx->pbioWrite) < 0) {
status = outctx->rc; break;
}
rblock = APR_BLOCK_READ; /* and try again with a blocking read. */
status = APR_SUCCESS; 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);
}
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; /* total length buffered */ int eos = 0; /* non-zero once EOS is seen */
/* Create the context which will be passed to the input filter; *containingasetasidepoolandabrigadewhichconstrainthe
* lifetime of the buffered data. */
ctx = apr_palloc(r->pool, sizeof *ctx);
ctx->bb = apr_brigade_create(r->pool, c->bucket_alloc);
/* ... and a temporary brigade. */
tempb = apr_brigade_create(r->pool, c->bucket_alloc);
/* The request body is read from the protocol-level input *filters;thebufferingfilterwillreinjectitfromthat *level,allowingcontent/resourcefilterstorunlater,if
* necessary. */
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, APLOGNO(02015) "could not read request body for SSL buffer"); return ap_map_http_request_error(rv, HTTP_INTERNAL_SERVER_ERROR);
}
/* Iterate through the returned brigade: setaside each bucket
* into the context's pool and move it into the brigade. */ for (e = APR_BRIGADE_FIRST(tempb);
e != APR_BRIGADE_SENTINEL(tempb) && !eos; e = next) { constchar *data;
apr_size_t len;
next = APR_BUCKET_NEXT(e);
if (APR_BUCKET_IS_EOS(e)) {
eos = 1;
} elseif (!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, APLOGNO(02016) "could not read bucket for SSL buffer"); return HTTP_INTERNAL_SERVER_ERROR;
}
total += len;
}
rv = apr_bucket_setaside(e, r->pool); if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02017) "could not setaside bucket for SSL buffer"); return HTTP_INTERNAL_SERVER_ERROR;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, "total of %" APR_OFF_T_FMT " bytes in buffer, eos=%d",
total, eos);
/* Fail if this exceeds the maximum buffer size. */ if (total > maxlen) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02018) "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);
/* After consuming all protocol-level input, remove all protocol-level *filters.Itshouldstrictlyonlybenecessarytoremovefilters *atexactlyftype==AP_FTYPE_PROTOCOL,sincethisfilterwill
* precede all > AP_FTYPE_PROTOCOL anyway. */ while (r->proto_input_filters->frec->ftype < AP_FTYPE_CONNECTION) {
ap_remove_input_filter(r->proto_input_filters);
}
/* Insert the filter which will supply the buffered content. */
ap_add_input_filter(ssl_io_buffer, ctx, r, c);
return0;
}
/* This input filter supplies the buffered request body to the caller *fromthebrigadestoredinf->ctx.Notethattheplacementofthis *filterinthefilterstackisimportant;itmustbethefirst *r->proto_input_filter;lower-typedfilterswillnotbepreserved
* across internal redirects (see PR 43738). */ 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;
apr_bucket *e, *d;
if (APR_BRIGADE_EMPTY(ctx->bb)) { /* Surprisingly (and perhaps, wrongly), the request body can be *pulledfromtheinputfilterstackmorethanonce;a *handlermayreadit,andap_discard_request_body()will *attempttodosoagainafter*every*request.Soinput *filtersmustbepreparedtogiveupanEOSifinvokedafter *initiallyreadingtherequest.TheHTTP_INfilterdoesthis
* with its ->eos_sent flag. */
if (mode == AP_MODE_READBYTES) { /* Partition the buffered brigade. */
rv = apr_brigade_partition(ctx->bb, bytes, &e); if (rv && rv != APR_INCOMPLETE) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02019) "could not partition buffered SSL brigade");
ap_remove_input_filter(f); return rv;
}
/* If the buffered brigade contains less then the requested
* length, just pass it all back. */ if (rv == APR_INCOMPLETE) {
APR_BRIGADE_CONCAT(bb, ctx->bb);
} else {
d = APR_BRIGADE_FIRST(ctx->bb);
e = APR_BUCKET_PREV(e);
/* Unsplice the partitioned segment and move it into the *passed-inbrigade;noconvenientwaytodothiswith
* the APR_BRIGADE_* macros. */
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 { /* Split a line into the passed-in brigade. */
rv = apr_brigade_split_line(bb, ctx->bb, block, bytes);
if (rv) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, f->c, APLOGNO(02020) "could not split line from buffered SSL brigade");
ap_remove_input_filter(f); return rv;
}
}
if (APR_BRIGADE_EMPTY(ctx->bb)) {
e = APR_BRIGADE_LAST(bb);
/* Ensure that the brigade is terminated by an EOS if the
* buffered request body has been entirely consumed. */ 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_TRACE4, 0, f->c, "buffered SSL brigade exhausted"); /* Note that the filter must *not* be removed here; it may be
* invoked again, see comment above. */
}
return APR_SUCCESS;
}
/* The request_rec pointer is passed in here only to ensure that the *filterchainismodifiedcorrectlywhendoingaTLSupgrade.It
* must *not* be used otherwise. */ staticvoid ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c,
request_rec *r, SSL *ssl)
{
bio_filter_in_ctx_t *inctx;
/* The request_rec pointer is passed in here only to ensure that the *filterchainismodifiedcorrectlywhendoingaTLSupgrade.It
* must *not* be used otherwise. */ void ssl_io_filter_init(conn_rec *c, request_rec *r, SSL *ssl)
{
ssl_filter_ctx_t *filter_ctx;
/* write is non blocking for the benefit of async mpm */ if (c->cs) {
BIO_set_nbio(filter_ctx->pbioWrite, 1);
ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, "Enabling non-blocking writes");
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.133Bemerkung:
(vorverarbeitet am 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.