From: Justin Erenkrantz Date: Thu, 11 Oct 2001 01:49:21 +0000 (+0000) Subject: This is the mod_ssl input filtering rewrite. Lots of stuff here. I also X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c4150ee9ae94b7f93e95fb03431e6ee975e1d049;p=thirdparty%2Fapache%2Fhttpd.git 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/modules/ssl@91414 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/mod_ssl.c b/mod_ssl.c index 69febc46d72..cbb2ea19a48 100644 --- a/mod_ssl.c +++ b/mod_ssl.c @@ -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; } diff --git a/mod_ssl.h b/mod_ssl.h index 4ec3286e1ba..07c7c776910 100644 --- a/mod_ssl.h +++ b/mod_ssl.h @@ -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 { diff --git a/ssl_engine_io.c b/ssl_engine_io.c index ff077e8da7a..b8f5665a84d 100644 --- a/ssl_engine_io.c +++ b/ssl_engine_io.c @@ -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,75 +189,67 @@ 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; + apr_bucket *e; + int found_eos = 0, n; + char buf[1024]; + apr_status_t rv; - 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; + /* Flush the output buffers. */ + churn_output(pRec); + + /* We have something in the processed brigade. Use that first. */ + if (!APR_BRIGADE_EMPTY(ctx->b)) { + return APR_SUCCESS; } - APR_BRIGADE_FOREACH(pbktIn,pRec->pbbInput) { - const char *data; - apr_size_t len; - int n; - char buf[1024]; - apr_status_t ret; + /* 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 (rv != APR_SUCCESS) + return rv; + + /* Can't make any progress here. */ + if (*readbytes == 0) + { + /* 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; + } + } - if (APR_BUCKET_IS_EOS(pbktIn)) { - break; - } + /* Process anything we have that we haven't done so already. */ + while (!APR_BRIGADE_EMPTY(ctx->rawb)) { + const char *data; + apr_size_t len; - /* read filter */ - ret = apr_bucket_read(pbktIn, &data, &len, (apr_read_type_e)eMode); + e = APR_BRIGADE_FIRST(ctx->rawb); - if (!(eMode == AP_MODE_NONBLOCKING && APR_STATUS_IS_EAGAIN(ret))) { - /* allow retry */ - APR_BUCKET_REMOVE(pbktIn); + if (APR_BUCKET_IS_EOS(e)) { + apr_bucket_delete(e); + found_eos = 1; + break; } - if (ret == APR_SUCCESS && len == 0 && eMode == AP_MODE_BLOCKING) - ret = APR_EOF; - - 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 - */ - return APR_SUCCESS; - } - - if (eMode != AP_MODE_NONBLOCKING) - ap_log_error(APLOG_MARK,APLOG_ERR,ret,NULL, - "Read failed in ssl input filter"); - - if ((eMode == AP_MODE_NONBLOCKING) && - (APR_STATUS_IS_SUCCESS(ret) || APR_STATUS_IS_EAGAIN(ret))) - { - /* In this case, we have data in the output bucket, or we were - * non-blocking, so returning nothing is fine. - */ - return APR_SUCCESS; - } - else { - return ret; - } - } + /* read from the bucket */ + rv = apr_bucket_read(e, &data, &len, eMode); + + if (rv != APR_SUCCESS) + return rv; - n = BIO_write (pRec->pbioRead, data, len); + /* Write it to our BIO */ + n = BIO_write(pRec->pbioRead, data, len); if ((apr_size_t)n != len) { /* this should never really happen, since we're just writing @@ -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 - */ - if (ret == HTTP_BAD_REQUEST) { - return APR_SUCCESS; - } - return ret; + } + + /* 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. + */ + 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; } + 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; - 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); - - /* Once we've read something, we can move to non-blocking mode (if - * we weren't already). - */ - eMode = AP_MODE_NONBLOCKING; - } - - ret=churn_output(pRec); - if(ret != APR_SUCCESS) - return ret; + /* 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); + e = apr_bucket_pool_create(pbuf, n, p); + APR_BRIGADE_INSERT_TAIL(ctx->b, e); + + /* Flush the output buffers. */ + churn_output(pRec); + } + + 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; - } - if (!APR_BUCKET_IS_FLUSH(bucket)) { + 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; + } + + /* 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,15 +401,14 @@ 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) { - break; + /* churn the state machine */ + if ((ret = churn_output(ctx)) != APR_SUCCESS) { + break; + } } + + apr_bucket_delete(bucket); } apr_brigade_destroy(bb); @@ -376,23 +417,101 @@ static apr_status_t ssl_io_filter_Output(ap_filter_t *f, 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; } - /* churn the state machine */ - ret = churn_input(pRec, eMode, readbytes); - if (ret != APR_SUCCESS) - return ret; + /* Return the requested amount or less. */ + if (*readbytes) + { + apr_bucket_brigade *newbb; + + /* churn the state machine */ + 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);