Commit 3fdafd25 authored by Justin Erenkrantz's avatar Justin Erenkrantz
Browse files

This is the mod_ssl input filtering rewrite. Lots of stuff here. I also

changed some of the style issues within the filtering code to conform to
the rest of the server.

Various incarnations of this patch have been posted to dev@httpd without
feedback.  Now that it passes all of the httpd-test cases (with the
exception of module/negotiation test which fails without mod_ssl anyway),
it is time to check it in.

Please review and test.  We are under C-T-R rules, so I'm going to take
advantage of that and commit it now.  I have tested this about as much
as I can and it seems to work from everything I can give to it.
Considering that mod_ssl was broken before this commit, this is an
improvement.


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@91414 13f79535-47bb-0310-9956-ffa450edef68
parent d6990f80
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
Changes with Apache 2.0.26-dev

  *) rewrite mod_ssl input filtering to work with the new input filtering
     system.  [Justin Erenkrantz]

  *) prefork: Don't segfault when we are able to listen on some but
     not all of the configured ports.  [Jeff Trawick]

+4 −7
Original line number Diff line number Diff line
APACHE 2.0 STATUS:						-*-text-*-
Last modified at [$Date: 2001/10/10 19:43:37 $]
Last modified at [$Date: 2001/10/11 01:49:21 $]

Release:

@@ -97,12 +97,9 @@ RELEASE SHOWSTOPPERS:
      fix the bug where request body content will end up closing the
      connection (buggering up persistent conns).
      Status: Justin is working on this as fast as he can.
              The core input filters and HTTP-related filters are
              switched to the new logic.  At this point, proxy and
              ssl remain broken.  Preliminary patches for ssl have been 
              posted to dev@httpd.  There are lots of places that made 
              implementation assumptions about the fact that the core 
              filter would return the socket.  That code is now broken.
              The core input filters, HTTP-related filters, and mod_ssl 
              are switched to the new logic.  At this point, proxy may be
              broken (Ian says it works, but dechunking is a bit shaky).  

      - socket bucket and core input filter changes. see end of
        message ID (Feb 27): <20010227075326.S2297@lyra.org>
+3 −37
Original line number Diff line number Diff line
@@ -73,8 +73,6 @@
        AP_INIT_##args("SSL"#name, ssl_cmd_SSL##name, NULL, OR_##type, desc),
#define AP_END_CMD { NULL }

#define HTTP_ON_HTTPS_PORT "GET /mod_ssl:error:HTTP-request HTTP/1.0\r\n"

static const command_rec ssl_config_cmds[] = {

    /*
@@ -363,47 +361,15 @@ int ssl_hook_process_connection(SSLFilterRec *pRec)
                 * borrowed from openssl_state_machine.c [mod_tls].
                 * TBD.
                 */
                return 0;
                return SSL_ERROR_WANT_READ;
            }
            else if (ERR_GET_REASON(ERR_peek_error()) == SSL_R_HTTP_REQUEST) {
                /*
                 * The case where OpenSSL has recognized a HTTP request:
                 * This means the client speaks plain HTTP on our HTTPS port.
                 * Hmmmm...  At least for this error we can be more friendly
                 * and try to provide him with a HTML error page. We have only
                 * one problem:OpenSSL has already read some bytes from the HTTP
                 * request. So we have to skip the request line manually and
                 * instead provide a faked one in order to continue the internal
                 * Apache processing.
                 *
                 * Hmmmm...  Punt this out of here after removing our output
                 * filter.
                 */
                apr_bucket *e;
                const char *str;
                apr_size_t len;
                /* log the situation */
                ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
                        "SSL handshake failed: HTTP spoken on HTTPS port; "
                        "trying to send HTML error page");

                /* fake the request line */
                e = apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT, 
                                               strlen(HTTP_ON_HTTPS_PORT));
                APR_BRIGADE_INSERT_HEAD(pRec->pbbPendingInput, e);

                APR_BRIGADE_FOREACH(e, pRec->pbbInput) {
                    apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ);
                    if (len) {
                        APR_BUCKET_REMOVE(e);
                        APR_BRIGADE_INSERT_TAIL(pRec->pbbPendingInput, e);
                        if ((strcmp(str, "\r\n") == 0) ||
                            (ap_strstr_c(str, "\r\n\r\n"))) {
                            break;
                        }
                    }
                }
                e = APR_BRIGADE_LAST(pRec->pbbInput);
                APR_BUCKET_REMOVE(e);

                ap_remove_output_filter(pRec->pOutputFilter);
                return HTTP_BAD_REQUEST;
            }
+2 −2
Original line number Diff line number Diff line
@@ -442,8 +442,8 @@ typedef struct {
    BIO                *pbioWrite;
    ap_filter_t        *pInputFilter;
    ap_filter_t        *pOutputFilter;
    apr_bucket_brigade *pbbInput;        /* encrypted input */
    apr_bucket_brigade *pbbPendingInput; /* decrypted input */
    apr_bucket_brigade *rawb;               /* encrypted input */
    apr_bucket_brigade *b;                  /* decrypted input */
} SSLFilterRec;

typedef struct {
+241 −122
Original line number Diff line number Diff line
@@ -72,7 +72,7 @@

static const char ssl_io_filter[] = "SSL/TLS Filter";

static int ssl_io_hook_read(SSL *ssl, unsigned char *buf, int len)
static int ssl_io_hook_read(SSL *ssl, char *buf, int len)
{
    int rc;

@@ -189,74 +189,66 @@ static apr_status_t churn_output(SSLFilterRec *ctx)

#define bio_is_renegotiating(bio) \
(((int)BIO_get_callback_arg(bio)) == SSL_ST_RENEGOTIATE)
#define HTTP_ON_HTTPS_PORT "GET /mod_ssl:error:HTTP-request HTTP/1.0\r\n"

static apr_status_t churn_input(SSLFilterRec *pRec,
        ap_input_mode_t eMode, apr_off_t *readbytes)
static apr_status_t churn_input(SSLFilterRec *pRec, ap_input_mode_t eMode, 
                                apr_off_t *readbytes)
{
    conn_rec *c = pRec->pInputFilter->c;
    ap_filter_t *f = pRec->pInputFilter;
    SSLFilterRec *ctx = pRec;
    conn_rec *c = f->c;
    apr_pool_t *p = c->pool;
    apr_bucket *pbktIn;

    if(APR_BRIGADE_EMPTY(pRec->pbbInput)) {
	ap_get_brigade(pRec->pInputFilter->next,pRec->pbbInput,eMode,readbytes);
	if(APR_BRIGADE_EMPTY(pRec->pbbInput))
	    return APR_EOF;
    }

    APR_BRIGADE_FOREACH(pbktIn,pRec->pbbInput) {
	const char *data;
	apr_size_t len;
	int n;
    apr_bucket *e;
    int found_eos = 0, n;
    char buf[1024];
	apr_status_t ret;

	if (APR_BUCKET_IS_EOS(pbktIn)) {
	    break;
	}

	/* read filter */
	ret = apr_bucket_read(pbktIn, &data, &len, (apr_read_type_e)eMode);

        if (!(eMode == AP_MODE_NONBLOCKING && APR_STATUS_IS_EAGAIN(ret))) {
            /* allow retry */
            APR_BUCKET_REMOVE(pbktIn);
        }
    apr_status_t rv;

	if (ret == APR_SUCCESS && len == 0 && eMode == AP_MODE_BLOCKING)
	    ret = APR_EOF;
    /* Flush the output buffers. */
    churn_output(pRec);

        if (len == 0) {
            /* Lazy frickin browsers just reset instead of shutting down. */
            /* also gotta handle timeout of keepalive connections */
            if (ret == APR_EOF || APR_STATUS_IS_ECONNRESET(ret) ||
                ret == APR_TIMEUP)
            {
                if (APR_BRIGADE_EMPTY(pRec->pbbPendingInput))
		    return APR_EOF;
		else
		    /* Next time around, the incoming brigade will be empty,
		     * so we'll return EOF then
		     */
    /* We have something in the processed brigade.  Use that first. */
    if (!APR_BRIGADE_EMPTY(ctx->b)) {
        return APR_SUCCESS;
    }

	    if (eMode != AP_MODE_NONBLOCKING)
		ap_log_error(APLOG_MARK,APLOG_ERR,ret,NULL,
			     "Read failed in ssl input filter");
    /* If we have nothing in the raw brigade, get some more. */
    if (APR_BRIGADE_EMPTY(ctx->rawb)) {
        rv = ap_get_brigade(f->next, ctx->rawb, eMode, readbytes);

	    if ((eMode == AP_MODE_NONBLOCKING) &&
                (APR_STATUS_IS_SUCCESS(ret) || APR_STATUS_IS_EAGAIN(ret)))
        if (rv != APR_SUCCESS)
            return rv;

        /* Can't make any progress here. */
        if (*readbytes == 0)
        {
                /* In this case, we have data in the output bucket, or we were
                 * non-blocking, so returning nothing is fine.
                 */
            /* This means that we have nothing else to read ever. */
            if (eMode == AP_MODE_BLOCKING) {
                APR_BRIGADE_INSERT_TAIL(ctx->b, apr_bucket_eos_create());
            }
            return APR_SUCCESS;
        }
            else {
                return ret;
    }

    /* Process anything we have that we haven't done so already. */
    while (!APR_BRIGADE_EMPTY(ctx->rawb)) {
        const char *data;
        apr_size_t len;

        e = APR_BRIGADE_FIRST(ctx->rawb);

        if (APR_BUCKET_IS_EOS(e)) {
            apr_bucket_delete(e);
            found_eos = 1;
            break;
        }

        /* read from the bucket */
        rv = apr_bucket_read(e, &data, &len, eMode);

        if (rv != APR_SUCCESS)
            return rv;

        /* Write it to our BIO */
	    n = BIO_write(pRec->pbioRead, data, len);
        
        if ((apr_size_t)n != len) {
@@ -270,48 +262,81 @@ static apr_status_t churn_input(SSLFilterRec *pRec,
            return APR_ENOMEM;
        }

        if (bio_is_renegotiating(pRec->pbioRead)) {
            /* we're doing renegotiation in the access phase */
            if (len >= *readbytes) {
                /* trick ap_http_filter into leaving us alone */
                *readbytes = 0;
                break; /* SSL_renegotiate will take it from here */
            }
        /* If we reached here, we read the bucket successfully, so toss
         * it from the raw brigade. */
        apr_bucket_delete(e);

    }

        if ((ret = ssl_hook_process_connection(pRec)) != APR_SUCCESS) {
            /* if this is the case, ssl connection has been shutdown
             * and pRec->pssl has been freed
    /* Flush the output buffers. */
    churn_output(pRec);

    /* Before we actually read any unencrypted data, go ahead and
     * let ssl_hook_process_connection have a shot at it. 
     */
            if (ret == HTTP_BAD_REQUEST) {
    rv = ssl_hook_process_connection(pRec);

    /* Flush again. */
    churn_output(pRec);

    if (rv != APR_SUCCESS) {
        /* if process connection says HTTP_BAD_REQUEST, we've seen a 
         * HTTP on HTTPS error.
         *
         * The case where OpenSSL has recognized a HTTP request:
         * This means the client speaks plain HTTP on our HTTPS port.
         * Hmmmm...  At least for this error we can be more friendly
         * and try to provide him with a HTML error page. We have only
         * one problem:OpenSSL has already read some bytes from the HTTP
         * request. So we have to skip the request line manually and
         * instead provide a faked one in order to continue the internal
         * Apache processing.
         *
         */
        if (rv == HTTP_BAD_REQUEST) {
            /* log the situation */
            ssl_log(c->base_server, SSL_LOG_ERROR|SSL_ADD_SSLERR,
                    "SSL handshake failed: HTTP spoken on HTTPS port; "
                    "trying to send HTML error page");

            /* fake the request line */
            e = apr_bucket_immortal_create(HTTP_ON_HTTPS_PORT,
                                           sizeof(HTTP_ON_HTTPS_PORT) - 1);
            APR_BRIGADE_INSERT_TAIL(ctx->b, e);
            e = apr_bucket_immortal_create(CRLF, sizeof(CRLF) - 1);
            APR_BRIGADE_INSERT_TAIL(ctx->b, e);

            return APR_SUCCESS;
        }
            return ret;
        if (rv == SSL_ERROR_WANT_READ) {
            apr_off_t tempread = 1024;
            return churn_input(pRec, eMode, &tempread);
        }
        return rv;
    }

        /* pass along all of the current BIO */
        while ((n = ssl_io_hook_read(pRec->pssl,
                                     (unsigned char *)buf, sizeof(buf))) > 0)
        {
	    apr_bucket *pbktOut;
    /* try to pass along all of the current BIO to ctx->b */
    /* FIXME: If there's an error and there was EOS, we may not really
     * reach EOS.
     */
    while ((n = ssl_io_hook_read(pRec->pssl, buf, sizeof(buf))) > 0) {
        char *pbuf;

        pbuf = apr_pmemdup(p, buf, n);
	    /* XXX: should we use a heap bucket instead? Or a transient (in
	     * which case we need a separate brigade for each bucket)?
	     */
	    pbktOut = apr_bucket_pool_create(pbuf, n, p);
	    APR_BRIGADE_INSERT_TAIL(pRec->pbbPendingInput,pbktOut);
        e = apr_bucket_pool_create(pbuf, n, p);
        APR_BRIGADE_INSERT_TAIL(ctx->b, e);

           /* Once we've read something, we can move to non-blocking mode (if
            * we weren't already).
            */
            eMode = AP_MODE_NONBLOCKING;
        /* Flush the output buffers. */
        churn_output(pRec);
    }

	ret=churn_output(pRec);
	if(ret != APR_SUCCESS)
	    return ret;
    if (n < 0 && errno == EINTR && APR_BRIGADE_EMPTY(ctx->b)) {
        apr_off_t tempread = 1024;
        return churn_input(pRec, eMode, &tempread);
    }

    if (found_eos) {
        APR_BRIGADE_INSERT_TAIL(ctx->b, apr_bucket_eos_create());
    }

    return churn_output(pRec);
@@ -324,19 +349,36 @@ static apr_status_t ssl_io_filter_Output(ap_filter_t *f,
    apr_bucket *bucket;
    apr_status_t ret = APR_SUCCESS;

    APR_BRIGADE_FOREACH(bucket, bb) {
    while (!APR_BRIGADE_EMPTY(bb)) {
        const char *data;
        apr_size_t len, n;

        if (APR_BUCKET_IS_EOS(bucket)) {
        bucket = APR_BRIGADE_FIRST(bb);

        /* If it is a flush or EOS, we need to pass this down. 
         * These types do not require translation by OpenSSL.  
         */
        if (APR_BUCKET_IS_EOS(bucket) || APR_BUCKET_IS_FLUSH(bucket)) {
            apr_bucket_brigade *outbb;

            if ((ret = churn_output(ctx)) != APR_SUCCESS) {
                ap_log_error(APLOG_MARK, APLOG_ERR, ret, NULL,
                             "Error in churn_output");
                return ret;
            }
            break;

            outbb = apr_brigade_create(f->c->pool);
            APR_BUCKET_REMOVE(bucket);
            APR_BRIGADE_INSERT_TAIL(outbb, bucket);
            ret = ap_pass_brigade(f->next, outbb);
            if (ret != APR_SUCCESS) {
                return ret;
            }

        if (!APR_BUCKET_IS_FLUSH(bucket)) {
            /* By definition, nothing can come after EOS. */
            if (APR_BUCKET_IS_EOS(bucket)) {
                break;
            }
        }
        else {
            /* read filter */
            apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);

@@ -359,10 +401,6 @@ static apr_status_t ssl_io_filter_Output(ap_filter_t *f,
                ret = APR_EINVAL;
                break;
            }
        }
        /* else fallthrough to flush the current wbio
         * likely triggered by renegotiation in ssl_hook_Access
         */

            /* churn the state machine */
            if ((ret = churn_output(ctx)) != APR_SUCCESS) {
@@ -370,29 +408,110 @@ static apr_status_t ssl_io_filter_Output(ap_filter_t *f,
            }
        }

        apr_bucket_delete(bucket);
    }

    apr_brigade_destroy(bb);
    return ret;
}

static apr_status_t ssl_io_filter_Input(ap_filter_t *f,
                                        apr_bucket_brigade *pbbOut,
                                        ap_input_mode_t eMode,
                                        ap_input_mode_t mode,
                                        apr_off_t *readbytes)
{
    apr_status_t ret;
    SSLFilterRec *pRec = f->ctx;
    SSLFilterRec *ctx = f->ctx;
    apr_status_t rv;
    apr_bucket *e;
    apr_off_t tempread;

    /* XXX: we don't currently support peek */
    if (eMode == AP_MODE_PEEK) {
    /* XXX: we don't currently support peek or readbytes == -1 */
    if (mode == AP_MODE_PEEK || *readbytes == -1) {
        return APR_ENOTIMPL;
    }

    /* Return the requested amount or less. */
    if (*readbytes)
    {
        apr_bucket_brigade *newbb;

        /* churn the state machine */
    ret = churn_input(pRec, eMode, readbytes);
        ret = churn_input(ctx, mode, readbytes);

        if (ret != APR_SUCCESS)
	        return ret;

    APR_BRIGADE_CONCAT(pbbOut, pRec->pbbPendingInput);
        /* ### This is bad. */
        APR_BRIGADE_NORMALIZE(ctx->b);

        apr_brigade_length(ctx->b, 0, &tempread);

        if (*readbytes < tempread) {
            tempread = *readbytes;
        } 
        else {
            *readbytes = tempread;
        }
        
        apr_brigade_partition(ctx->b, tempread, &e);
        newbb = apr_brigade_split(ctx->b, e);
        APR_BRIGADE_CONCAT(pbbOut, ctx->b);
        APR_BRIGADE_CONCAT(ctx->b, newbb);

        return APR_SUCCESS;
    }
   
    /* Readbytes == 0 implies we only want a LF line. 
     * 1024 seems like a good number for now. */
    if (APR_BRIGADE_EMPTY(ctx->b)) {
        tempread = 1024; 
        rv = churn_input(ctx, mode, &tempread);
        if (rv != APR_SUCCESS)
            return rv;
        /* We have already blocked. */
        mode = AP_MODE_NONBLOCKING;
    }
    while (!APR_BRIGADE_EMPTY(ctx->b)) {
        const char *pos, *str;
        apr_size_t len;

        e = APR_BRIGADE_FIRST(ctx->b);

        /* Sure, we'll call this is a line.  Whatever. */
        if (APR_BUCKET_IS_EOS(e)) {
            APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(pbbOut, e);
            break;
        }

        if ((rv = apr_bucket_read(e, &str, &len, 
                                  AP_MODE_NONBLOCKING)) != APR_SUCCESS) {
            return rv;
        }

        pos = memchr(str, APR_ASCII_LF, len);
        /* We found a match. */
        if (pos != NULL) {
            apr_bucket_split(e, pos - str + 1);
            APR_BUCKET_REMOVE(e);
            APR_BRIGADE_INSERT_TAIL(pbbOut, e);
            *readbytes += pos - str;
            return APR_SUCCESS;
        }
        APR_BUCKET_REMOVE(e);
        APR_BRIGADE_INSERT_TAIL(pbbOut, e);
        *readbytes += len;

        /* Hey, we're about to be starved - go fetch more data. */
        if (APR_BRIGADE_EMPTY(ctx->b)) {
            tempread = 1024;
            ret = churn_input(ctx, mode, &tempread);
            if (ret != APR_SUCCESS)
	            return ret;
            mode = AP_MODE_NONBLOCKING;
        }
    }

    return APR_SUCCESS;
}
@@ -423,8 +542,8 @@ void ssl_io_filter_init(conn_rec *c, SSL *ssl)
    filter = apr_pcalloc(c->pool, sizeof(SSLFilterRec));
    filter->pInputFilter    = ap_add_input_filter(ssl_io_filter, filter, NULL, c);
    filter->pOutputFilter   = ap_add_output_filter(ssl_io_filter, filter, NULL, c);
    filter->pbbInput        = apr_brigade_create(c->pool);
    filter->pbbPendingInput = apr_brigade_create(c->pool);
    filter->b               = apr_brigade_create(c->pool);
    filter->rawb            = apr_brigade_create(c->pool);
    filter->pbioRead        = BIO_new(BIO_s_mem());
    filter->pbioWrite       = BIO_new(BIO_s_mem());
    SSL_set_bio(ssl, filter->pbioRead, filter->pbioWrite);