diff options
author | Arno Töll <arno@debian.org> | 2014-07-20 06:23:00 -1100 |
---|---|---|
committer | Arno Töll <arno@debian.org> | 2014-07-20 06:23:00 -1100 |
commit | cb35beef2a938b80c9e4b5d6a408eca437aa74db (patch) | |
tree | 87ae6e0d2b3a8ce318fe8ab559494808a451035e /modules/proxy | |
parent | 2a463b3cd73c32ee9dcd508248d0194923f435f4 (diff) | |
download | apache2-cb35beef2a938b80c9e4b5d6a408eca437aa74db.tar.gz |
Imported Upstream version 2.4.10
Diffstat (limited to 'modules/proxy')
-rw-r--r-- | modules/proxy/ajp_header.c | 20 | ||||
-rw-r--r-- | modules/proxy/ajp_header.h | 5 | ||||
-rw-r--r-- | modules/proxy/mod_proxy.c | 40 | ||||
-rw-r--r-- | modules/proxy/mod_proxy.h | 10 | ||||
-rw-r--r-- | modules/proxy/mod_proxy_balancer.c | 2 | ||||
-rw-r--r-- | modules/proxy/mod_proxy_fcgi.c | 158 | ||||
-rw-r--r-- | modules/proxy/mod_proxy_http.c | 26 | ||||
-rw-r--r-- | modules/proxy/mod_proxy_scgi.c | 13 | ||||
-rw-r--r-- | modules/proxy/mod_proxy_wstunnel.c | 51 | ||||
-rw-r--r-- | modules/proxy/proxy_util.c | 306 | ||||
-rw-r--r-- | modules/proxy/scgi.h | 36 |
11 files changed, 439 insertions, 228 deletions
diff --git a/modules/proxy/ajp_header.c b/modules/proxy/ajp_header.c index 074f0a8d..8f9a2fcd 100644 --- a/modules/proxy/ajp_header.c +++ b/modules/proxy/ajp_header.c @@ -435,6 +435,26 @@ static apr_status_t ajp_marshal_into_msgb(ajp_msg_t *msg, return AJP_EOVERFLOW; } } + /* Forward the local ip address information, which was forgotten + * from the builtin data of the AJP 13 protocol. + * Since the servlet spec allows to retrieve it via getLocalAddr(), + * we provide the address to the Tomcat connector as a request + * attribute. Modern Tomcat versions know how to retrieve + * the local address from this attribute. + */ + { + const char *key = SC_A_REQ_LOCAL_ADDR; + char *val = r->connection->local_ip; + if (ajp_msg_append_uint8(msg, SC_A_REQ_ATTRIBUTE) || + ajp_msg_append_string(msg, key) || + ajp_msg_append_string(msg, val)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02646) + "ajp_marshal_into_msgb: " + "Error appending attribute %s=%s", + key, val); + return AJP_EOVERFLOW; + } + } /* Use the environment vars prefixed with AJP_ * and pass it to the header striping that prefix. */ diff --git a/modules/proxy/ajp_header.h b/modules/proxy/ajp_header.h index 22d89080..0f5fdaa8 100644 --- a/modules/proxy/ajp_header.h +++ b/modules/proxy/ajp_header.h @@ -51,6 +51,11 @@ * to contain the forwarded remote port. */ #define SC_A_REQ_REMOTE_PORT ("AJP_REMOTE_PORT") +/* + * The following request attribute is recognized by Tomcat + * to contain the forwarded local ip address. + */ +#define SC_A_REQ_LOCAL_ADDR ("AJP_LOCAL_ADDR") /* * Request methods, coded as numbers instead of strings. diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 627a0496..b331a6c5 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -927,8 +927,25 @@ static int proxy_handler(request_rec *r) struct dirconn_entry *list = (struct dirconn_entry *)conf->dirconn->elts; /* is this for us? */ - if (!r->proxyreq || !r->filename || strncmp(r->filename, "proxy:", 6) != 0) + if (!r->filename) { + return DECLINED; + } + + if (!r->proxyreq) { + /* We may have forced the proxy handler via config or .htaccess */ + if (r->handler && + strncmp(r->handler, "proxy:", 6) == 0 && + strncmp(r->filename, "proxy:", 6) != 0) { + r->proxyreq = PROXYREQ_REVERSE; + r->filename = apr_pstrcat(r->pool, r->handler, r->filename, NULL); + apr_table_setn(r->notes, "rewrite-proxy", "1"); + } + else { + return DECLINED; + } + } else if (strncmp(r->filename, "proxy:", 6) != 0) { return DECLINED; + } /* handle max-forwards / OPTIONS / TRACE */ if ((str = apr_table_get(r->headers_in, "Max-Forwards"))) { @@ -1584,13 +1601,26 @@ static const char * /* Distinguish the balancer from worker */ if (ap_proxy_valid_balancer_name(r, 9)) { proxy_balancer *balancer = ap_proxy_get_balancer(cmd->pool, conf, r, 0); + char *fake_copy; + + /* + * In the regex case supplying a fake URL doesn't make sense as it + * cannot be parsed anyway with apr_uri_parse later on in + * ap_proxy_define_balancer / ap_proxy_update_balancer + */ + if (use_regex) { + fake_copy = NULL; + } + else { + fake_copy = f; + } if (!balancer) { - const char *err = ap_proxy_define_balancer(cmd->pool, &balancer, conf, r, f, 0); + const char *err = ap_proxy_define_balancer(cmd->pool, &balancer, conf, r, fake_copy, 0); if (err) return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL); } else { - ap_proxy_update_balancer(cmd->pool, balancer, f); + ap_proxy_update_balancer(cmd->pool, balancer, fake_copy); } for (i = 0; i < arr->nelts; i++) { const char *err = set_balancer_param(conf, cmd->pool, balancer, elts[i].key, @@ -2618,6 +2648,8 @@ static void child_init(apr_pool_t *p, server_rec *s) ap_proxy_hashfunc(conf->forward->s->name, PROXY_HASHFUNC_FNV); /* Do not disable worker in case of errors */ conf->forward->s->status |= PROXY_WORKER_IGNORE_ERRORS; + /* Mark as the "generic" worker */ + conf->forward->s->status |= PROXY_WORKER_GENERIC; ap_proxy_initialize_worker(conf->forward, s, conf->pool); /* Disable address cache for generic forward worker */ conf->forward->s->is_address_reusable = 0; @@ -2633,6 +2665,8 @@ static void child_init(apr_pool_t *p, server_rec *s) ap_proxy_hashfunc(reverse->s->name, PROXY_HASHFUNC_FNV); /* Do not disable worker in case of errors */ reverse->s->status |= PROXY_WORKER_IGNORE_ERRORS; + /* Mark as the "generic" worker */ + reverse->s->status |= PROXY_WORKER_GENERIC; conf->reverse = reverse; ap_proxy_initialize_worker(conf->reverse, s, conf->pool); /* Disable address cache for generic reverse worker */ diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index eb0106d6..4ead22e7 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -254,6 +254,7 @@ typedef struct { * filter chain or not */ unsigned int inreslist:1; /* connection in apr_reslist? */ const char *uds_path; /* Unix domain socket path */ + const char *ssl_hostname;/* Hostname (SNI) in use by SSL connection */ } proxy_conn_rec; typedef struct { @@ -274,6 +275,7 @@ struct proxy_conn_pool { #define PROXY_WORKER_INITIALIZED 0x0001 #define PROXY_WORKER_IGNORE_ERRORS 0x0002 #define PROXY_WORKER_DRAIN 0x0004 +#define PROXY_WORKER_GENERIC 0x0008 #define PROXY_WORKER_IN_SHUTDOWN 0x0010 #define PROXY_WORKER_DISABLED 0x0020 #define PROXY_WORKER_STOPPED 0x0040 @@ -285,6 +287,7 @@ struct proxy_conn_pool { #define PROXY_WORKER_INITIALIZED_FLAG 'O' #define PROXY_WORKER_IGNORE_ERRORS_FLAG 'I' #define PROXY_WORKER_DRAIN_FLAG 'N' +#define PROXY_WORKER_GENERIC_FLAG 'G' #define PROXY_WORKER_IN_SHUTDOWN_FLAG 'U' #define PROXY_WORKER_DISABLED_FLAG 'D' #define PROXY_WORKER_STOPPED_FLAG 'S' @@ -305,6 +308,8 @@ PROXY_WORKER_DISABLED | PROXY_WORKER_STOPPED | PROXY_WORKER_IN_ERROR ) #define PROXY_WORKER_IS_DRAINING(f) ( (f)->s->status & PROXY_WORKER_DRAIN ) +#define PROXY_WORKER_IS_GENERIC(f) ( (f)->s->status & PROXY_WORKER_GENERIC ) + /* default worker retry timeout in seconds */ #define PROXY_WORKER_DEFAULT_RETRY 60 @@ -792,8 +797,9 @@ PROXY_DECLARE(int) ap_proxy_post_request(proxy_worker *worker, * @param url request url * @param proxyname are we connecting directly or via a proxy * @param proxyport proxy host port - * @param server_portstr Via headers server port - * @param server_portstr_size size of the server_portstr buffer + * @param server_portstr Via headers server port, must be non-NULL + * @param server_portstr_size size of the server_portstr buffer; must + * be at least one, even if the protocol doesn't use this * @return OK or HTTP_XXX error */ PROXY_DECLARE(int) ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c index c927d3bf..acfd3861 100644 --- a/modules/proxy/mod_proxy_balancer.c +++ b/modules/proxy/mod_proxy_balancer.c @@ -1602,7 +1602,7 @@ static int balancer_handler(request_rec *r) ap_rputs("<h3>Edit balancer settings for ", r); ap_rvputs(r, bsel->s->name, "</h3>\n", NULL); ap_rputs("<form method='POST' enctype='application/x-www-form-urlencoded' action='", r); - ap_rvputs(r, action, "'>\n", NULL); + ap_rvputs(r, ap_escape_uri(r->pool, action), "'>\n", NULL); ap_rputs("<dl>\n<table>\n", r); provs = ap_list_provider_names(r->pool, PROXY_LBMETHOD, "0"); if (provs) { diff --git a/modules/proxy/mod_proxy_fcgi.c b/modules/proxy/mod_proxy_fcgi.c index 4f407951..e2fb59cd 100644 --- a/modules/proxy/mod_proxy_fcgi.c +++ b/modules/proxy/mod_proxy_fcgi.c @@ -90,26 +90,13 @@ static int proxy_fcgi_canon(request_rec *r, char *url) static apr_status_t send_data(proxy_conn_rec *conn, struct iovec *vec, int nvec, - apr_size_t *len, - int blocking) + apr_size_t *len) { - apr_status_t rv = APR_SUCCESS, arv; + apr_status_t rv = APR_SUCCESS; apr_size_t written = 0, to_write = 0; int i, offset; - apr_interval_time_t old_timeout; apr_socket_t *s = conn->sock; - if (!blocking) { - arv = apr_socket_timeout_get(s, &old_timeout); - if (arv != APR_SUCCESS) { - return arv; - } - arv = apr_socket_timeout_set(s, 0); - if (arv != APR_SUCCESS) { - return arv; - } - } - for (i = 0; i < nvec; i++) { to_write += vec[i].iov_len; } @@ -118,7 +105,7 @@ static apr_status_t send_data(proxy_conn_rec *conn, while (to_write) { apr_size_t n = 0; rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); - if ((rv != APR_SUCCESS) && !APR_STATUS_IS_EAGAIN(rv)) { + if (rv != APR_SUCCESS) { break; } if (n > 0) { @@ -141,12 +128,6 @@ static apr_status_t send_data(proxy_conn_rec *conn, conn->worker->s->transferred += written; *len = written; - if (!blocking) { - arv = apr_socket_timeout_set(s, old_timeout); - if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { - return arv; - } - } return rv; } @@ -207,7 +188,7 @@ static apr_status_t send_begin_request(proxy_conn_rec *conn, vec[1].iov_base = (void *)abrb; vec[1].iov_len = sizeof(abrb); - return send_data(conn, vec, 2, &len, 1); + return send_data(conn, vec, 2, &len); } static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, @@ -294,7 +275,7 @@ static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, vec[1].iov_base = body; vec[1].iov_len = required_len; - rv = send_data(conn, vec, 2, &len, 1); + rv = send_data(conn, vec, 2, &len); apr_pool_clear(temp_pool); if (rv) { @@ -309,7 +290,7 @@ static apr_status_t send_environment(proxy_conn_rec *conn, request_rec *r, vec[0].iov_base = (void *)farray; vec[0].iov_len = sizeof(farray); - return send_data(conn, vec, 1, &len, 1); + return send_data(conn, vec, 1, &len); } enum { @@ -383,7 +364,8 @@ static int handle_headers(request_rec *r, static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, request_rec *r, apr_pool_t *setaside_pool, - apr_uint16_t request_id) + apr_uint16_t request_id, + const char **err) { apr_bucket_brigade *ib, *ob; int seen_end_of_headers = 0, done = 0; @@ -395,7 +377,16 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, unsigned char farray[AP_FCGI_HEADER_LEN]; apr_pollfd_t pfd; int header_state = HDR_STATE_READING_HEADERS; - + char stack_iobuf[AP_IOBUFSIZE]; + apr_size_t iobuf_size = AP_IOBUFSIZE; + char *iobuf = stack_iobuf; + + *err = NULL; + if (conn->worker->s->io_buffer_size_set) { + iobuf_size = conn->worker->s->io_buffer_size; + iobuf = apr_palloc(r->pool, iobuf_size); + } + pfd.desc_type = APR_POLL_SOCKET; pfd.desc.s = conn->sock; pfd.p = r->pool; @@ -418,19 +409,20 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, if (APR_STATUS_IS_EINTR(rv)) { continue; } + *err = "polling"; break; } if (pfd.rtnevents & APR_POLLOUT) { - char writebuf[AP_IOBUFSIZE]; - apr_size_t writebuflen; + apr_size_t to_send, writebuflen; int last_stdin = 0; - int nvec = 0; + char *iobuf_cursor; rv = ap_get_brigade(r->input_filters, ib, AP_MODE_READBYTES, APR_BLOCK_READ, - sizeof(writebuf)); + iobuf_size); if (rv != APR_SUCCESS) { + *err = "reading input brigade"; break; } @@ -438,30 +430,48 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, last_stdin = 1; } - writebuflen = sizeof(writebuf); + writebuflen = iobuf_size; - rv = apr_brigade_flatten(ib, writebuf, &writebuflen); + rv = apr_brigade_flatten(ib, iobuf, &writebuflen); apr_brigade_cleanup(ib); if (rv != APR_SUCCESS) { + *err = "flattening brigade"; break; } - ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, - (apr_uint16_t) writebuflen, 0); - ap_fcgi_header_to_array(&header, farray); + to_send = writebuflen; + iobuf_cursor = iobuf; + while (to_send > 0) { + int nvec = 0; + apr_size_t write_this_time; + + write_this_time = + to_send < AP_FCGI_MAX_CONTENT_LEN ? to_send : AP_FCGI_MAX_CONTENT_LEN; + + ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, + (apr_uint16_t)write_this_time, 0); + ap_fcgi_header_to_array(&header, farray); - vec[nvec].iov_base = (void *)farray; - vec[nvec].iov_len = sizeof(farray); - ++nvec; - if (writebuflen) { - vec[nvec].iov_base = writebuf; - vec[nvec].iov_len = writebuflen; + vec[nvec].iov_base = (void *)farray; + vec[nvec].iov_len = sizeof(farray); ++nvec; - } + if (writebuflen) { + vec[nvec].iov_base = iobuf_cursor; + vec[nvec].iov_len = write_this_time; + ++nvec; + } + + rv = send_data(conn, vec, nvec, &len); + if (rv != APR_SUCCESS) { + *err = "sending stdin"; + break; + } - rv = send_data(conn, vec, nvec, &len, 0); + to_send -= write_this_time; + iobuf_cursor += write_this_time; + } if (rv != APR_SUCCESS) { break; } @@ -469,33 +479,29 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, if (last_stdin) { pfd.reqevents = APR_POLLIN; /* Done with input data */ - if (writebuflen) { /* empty AP_FCGI_STDIN not already sent? */ - ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, - 0, 0); - ap_fcgi_header_to_array(&header, farray); + /* signal EOF (empty FCGI_STDIN) */ + ap_fcgi_fill_in_header(&header, AP_FCGI_STDIN, request_id, + 0, 0); + ap_fcgi_header_to_array(&header, farray); - vec[0].iov_base = (void *)farray; - vec[0].iov_len = sizeof(farray); + vec[0].iov_base = (void *)farray; + vec[0].iov_len = sizeof(farray); - rv = send_data(conn, vec, 1, &len, 1); + rv = send_data(conn, vec, 1, &len); + if (rv != APR_SUCCESS) { + *err = "sending empty stdin"; + break; } } } if (pfd.rtnevents & APR_POLLIN) { - /* readbuf has one byte on the end that is always 0, so it's - * able to work with a strstr when we search for the end of - * the headers, even if we fill the entire length in the recv. */ - char readbuf[AP_IOBUFSIZE + 1]; apr_size_t readbuflen; apr_uint16_t clen, rid; apr_bucket *b; unsigned char plen; unsigned char type, version; - memset(readbuf, 0, sizeof(readbuf)); - memset(farray, 0, sizeof(farray)); - /* First, we grab the header... */ rv = get_data_full(conn, (char *) farray, AP_FCGI_HEADER_LEN); if (rv != APR_SUCCESS) { @@ -528,8 +534,8 @@ static apr_status_t dispatch(proxy_conn_rec *conn, proxy_dir_conf *conf, } recv_again: - if (clen > sizeof(readbuf) - 1) { - readbuflen = sizeof(readbuf) - 1; + if (clen > iobuf_size) { + readbuflen = iobuf_size; } else { readbuflen = clen; } @@ -538,24 +544,24 @@ recv_again: * recv call, this will eventually change when we move to real * nonblocking recv calls. */ if (readbuflen != 0) { - rv = get_data(conn, readbuf, &readbuflen); + rv = get_data(conn, iobuf, &readbuflen); if (rv != APR_SUCCESS) { + *err = "reading response body"; break; } - readbuf[readbuflen] = 0; } switch (type) { case AP_FCGI_STDOUT: if (clen != 0) { - b = apr_bucket_transient_create(readbuf, + b = apr_bucket_transient_create(iobuf, readbuflen, c->bucket_alloc); APR_BRIGADE_INSERT_TAIL(ob, b); if (! seen_end_of_headers) { - int st = handle_headers(r, &header_state, readbuf); + int st = handle_headers(r, &header_state, iobuf); if (st == 1) { int status; @@ -591,9 +597,14 @@ recv_again: r->status = HTTP_OK; } - if (script_error_status == HTTP_OK) { + if (script_error_status == HTTP_OK + && !APR_BRIGADE_EMPTY(ob)) { + /* Send the part of the body that we read while + * reading the headers. + */ rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { + *err = "passing brigade to output filters"; break; } } @@ -618,6 +629,7 @@ recv_again: if (script_error_status == HTTP_OK) { rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { + *err = "passing brigade to output filters"; break; } } @@ -638,6 +650,7 @@ recv_again: APR_BRIGADE_INSERT_TAIL(ob, b); rv = ap_pass_brigade(r->output_filters, ob); if (rv != APR_SUCCESS) { + *err = "passing brigade to output filters"; break; } } @@ -650,7 +663,7 @@ recv_again: /* TODO: Should probably clean up this logging a bit... */ if (clen) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01071) - "Got error '%s'", readbuf); + "Got error '%s'", iobuf); } if (clen > readbuflen) { @@ -670,7 +683,7 @@ recv_again: } if (plen) { - rv = get_data_full(conn, readbuf, plen); + rv = get_data_full(conn, iobuf, plen); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02537) "Error occurred reading padding"); @@ -708,6 +721,7 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, apr_uint16_t request_id = 1; apr_status_t rv; apr_pool_t *temp_pool; + const char *err; /* Step 1: Send AP_FCGI_BEGIN_REQUEST */ rv = send_begin_request(conn, request_id); @@ -730,10 +744,14 @@ static int fcgi_do_request(apr_pool_t *p, request_rec *r, } /* Step 3: Read records from the back end server and handle them. */ - rv = dispatch(conn, conf, r, temp_pool, request_id); + rv = dispatch(conn, conf, r, temp_pool, request_id, &err); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01075) - "Error dispatching request to %s:", server_portstr); + "Error dispatching request to %s: %s%s%s", + server_portstr, + err ? "(" : "", + err ? err : "", + err ? ")" : ""); conn->close = 1; return HTTP_SERVICE_UNAVAILABLE; } @@ -754,7 +772,7 @@ static int proxy_fcgi_handler(request_rec *r, proxy_worker *worker, int status; char server_portstr[32]; conn_rec *origin = NULL; - proxy_conn_rec *backend; + proxy_conn_rec *backend = NULL; proxy_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &proxy_module); diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c index 3aec4cfe..141452bf 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -1306,7 +1306,7 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, "error reading status line from remote " "server %s:%d", backend->hostname, backend->port); if (APR_STATUS_IS_TIMEUP(rc)) { - apr_table_set(r->notes, "proxy_timedout", "1"); + apr_table_setn(r->notes, "proxy_timedout", "1"); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01103) "read timeout"); if (do_100_continue) { return ap_proxyerror(r, HTTP_SERVICE_UNAVAILABLE, "Timeout on 100-Continue"); @@ -1745,7 +1745,6 @@ apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r, #endif /* sanity check */ if (APR_BRIGADE_EMPTY(bb)) { - apr_brigade_cleanup(bb); break; } @@ -1977,25 +1976,10 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, * requested, such that mod_ssl can check if it is requested to do * so. */ - if (is_ssl) { - proxy_dir_conf *dconf; - const char *ssl_hostname; - - /* - * In the case of ProxyPreserveHost on use the hostname of - * the request if present otherwise use the one from the - * backend request URI. - */ - dconf = ap_get_module_config(r->per_dir_config, &proxy_module); - if ((dconf->preserve_host != 0) && (r->hostname != NULL)) { - ssl_hostname = r->hostname; - } - else { - ssl_hostname = uri->hostname; - } - - apr_table_set(backend->connection->notes, "proxy-request-hostname", - ssl_hostname); + if (backend->ssl_hostname) { + apr_table_setn(backend->connection->notes, + "proxy-request-hostname", + backend->ssl_hostname); } } diff --git a/modules/proxy/mod_proxy_scgi.c b/modules/proxy/mod_proxy_scgi.c index f77a986e..7fb2b873 100644 --- a/modules/proxy/mod_proxy_scgi.c +++ b/modules/proxy/mod_proxy_scgi.c @@ -37,13 +37,13 @@ #include "util_script.h" #include "mod_proxy.h" +#include "scgi.h" #define SCHEME "scgi" #define PROXY_FUNCTION "SCGI" #define SCGI_MAGIC "SCGI" #define SCGI_PROTOCOL_VERSION "1" -#define SCGI_DEFAULT_PORT (4000) /* just protect from typos */ #define CONTENT_LENGTH "CONTENT_LENGTH" @@ -176,13 +176,15 @@ static int scgi_canon(request_rec *r, char *url) { char *host, sport[sizeof(":65535")]; const char *err, *path; - apr_port_t port = SCGI_DEFAULT_PORT; + apr_port_t port, def_port; if (strncasecmp(url, SCHEME "://", sizeof(SCHEME) + 2)) { return DECLINED; } url += sizeof(SCHEME); /* Keep slashes */ + port = def_port = SCGI_DEF_PORT; + err = ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00857) @@ -190,7 +192,12 @@ static int scgi_canon(request_rec *r, char *url) return HTTP_BAD_REQUEST; } - apr_snprintf(sport, sizeof(sport), ":%u", port); + if (port != def_port) { + apr_snprintf(sport, sizeof(sport), ":%u", port); + } + else { + sport[0] = '\0'; + } if (ap_strchr(host, ':')) { /* if literal IPv6 address */ host = apr_pstrcat(r->pool, "[", host, "]", NULL); diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c index d17eaff6..a2172fe2 100644 --- a/modules/proxy/mod_proxy_wstunnel.c +++ b/modules/proxy/mod_proxy_wstunnel.c @@ -103,10 +103,12 @@ static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o, rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES, APR_NONBLOCK_READ, AP_IOBUFSIZE); if (rv == APR_SUCCESS) { - if (c_o->aborted) + if (c_o->aborted) { return APR_EPIPE; - if (APR_BRIGADE_EMPTY(bb)) + } + if (APR_BRIGADE_EMPTY(bb)) { break; + } #ifdef DEBUGGING len = -1; apr_brigade_length(bb, 0, &len); @@ -130,9 +132,12 @@ static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o, } } while (rv == APR_SUCCESS); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r, "wstunnel_transfer complete"); + if (APR_STATUS_IS_EAGAIN(rv)) { rv = APR_SUCCESS; } + return rv; } @@ -178,7 +183,6 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, conn_rec *c = r->connection; apr_socket_t *sock = conn->sock; conn_rec *backconn = conn->connection; - int client_error = 0; char *buf; apr_bucket_brigade *header_brigade; apr_bucket *e; @@ -224,7 +228,7 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, pollfd.p = p; pollfd.desc_type = APR_POLL_SOCKET; - pollfd.reqevents = APR_POLLIN; + pollfd.reqevents = APR_POLLIN | APR_POLLHUP; pollfd.desc.s = sock; pollfd.client_data = NULL; apr_pollset_add(pollset, &pollfd); @@ -232,13 +236,16 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, pollfd.desc.s = client_socket; apr_pollset_add(pollset, &pollfd); + remove_reqtimeout(c->input_filters); r->output_filters = c->output_filters; r->proto_output_filters = c->output_filters; r->input_filters = c->input_filters; r->proto_input_filters = c->input_filters; - remove_reqtimeout(r->input_filters); + /* This handler should take care of the entire connection; make it so that + * nothing else is attempted on the connection after returning. */ + c->keepalive = AP_CONN_CLOSE; while (1) { /* Infinite loop until error (one side closes the connection) */ if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled)) @@ -257,38 +264,34 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, if (cur->desc.s == sock) { pollevent = cur->rtnevents; - if (pollevent & APR_POLLIN) { + if (pollevent & (APR_POLLIN | APR_POLLHUP)) { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02446) "sock was readable"); rv = proxy_wstunnel_transfer(r, backconn, c, bb, "sock"); - } - else if ((pollevent & APR_POLLERR) - || (pollevent & APR_POLLHUP)) { - rv = APR_EPIPE; - ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447) - "err/hup on backconn"); + } + else if (pollevent & APR_POLLERR) { + rv = APR_EPIPE; + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447) + "error on backconn"); } else { rv = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02605) "unknown event on backconn %d", pollevent); } - if (rv != APR_SUCCESS) - client_error = 1; } else if (cur->desc.s == client_socket) { pollevent = cur->rtnevents; - if (pollevent & APR_POLLIN) { + if (pollevent & (APR_POLLIN | APR_POLLHUP)) { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02448) "client was readable"); rv = proxy_wstunnel_transfer(r, c, backconn, bb, "client"); } - else if ((pollevent & APR_POLLERR) - || (pollevent & APR_POLLHUP)) { + else if (pollevent & APR_POLLERR) { rv = APR_EPIPE; c->aborted = 1; ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607) - "err/hup on client conn"); + "error on client conn"); } else { rv = APR_EGENERAL; @@ -311,9 +314,6 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "finished with poll() - cleaning up"); - if (client_error) { - return HTTP_INTERNAL_SERVER_ERROR; - } return OK; } @@ -332,9 +332,11 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker, conn_rec *c = r->connection; apr_pool_t *p = r->pool; apr_uri_t *uri; + int is_ssl = 0; if (strncasecmp(url, "wss:", 4) == 0) { scheme = "WSS"; + is_ssl = 1; } else if (strncasecmp(url, "ws:", 3) == 0) { scheme = "WS"; @@ -358,7 +360,7 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker, return status; } - backend->is_ssl = 0; + backend->is_ssl = is_ssl; backend->close = 0; retry = 0; @@ -386,7 +388,10 @@ static int proxy_wstunnel_handler(request_rec *r, proxy_worker *worker, if ((status = ap_proxy_connection_create(scheme, backend, c, r->server)) != OK) break; - } + } + + backend->close = 1; /* must be after ap_proxy_determine_connection */ + /* Step Three: Process the Request */ status = ap_proxy_wstunnel_request(p, r, backend, worker, conf, uri, locurl, diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c index d05f0cdc..bc840499 100644 --- a/modules/proxy/proxy_util.c +++ b/modules/proxy/proxy_util.c @@ -22,6 +22,7 @@ #include "apr_hash.h" #include "proxy_util.h" #include "ajp.h" +#include "scgi.h" #if APR_HAVE_UNISTD_H #include <unistd.h> /* for getpid() */ @@ -1123,6 +1124,9 @@ PROXY_DECLARE(char *) ap_proxy_update_balancer(apr_pool_t *p, const char *url) { apr_uri_t puri; + if (!url) { + return NULL; + } if (apr_uri_parse(p, url, &puri) != APR_SUCCESS) { return apr_psprintf(p, "unable to parse: %s", url); } @@ -1402,6 +1406,7 @@ static void socket_cleanup(proxy_conn_rec *conn) { conn->sock = NULL; conn->connection = NULL; + conn->ssl_hostname = NULL; apr_pool_clear(conn->scpool); } @@ -1927,6 +1932,8 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, } else if (r->proxyreq == PROXYREQ_REVERSE) { if (conf->reverse) { + char *ptr; + char *ptr2; ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "*: found reverse proxy worker for %s", *url); *balancer = NULL; @@ -1938,6 +1945,36 @@ PROXY_DECLARE(int) ap_proxy_pre_request(proxy_worker **worker, * regarding the Connection header in the request. */ apr_table_setn(r->subprocess_env, "proxy-nokeepalive", "1"); + /* + * In the case of the generic reverse proxy, we need to see if we + * were passed a UDS url (eg: from mod_proxy) and adjust uds_path + * as required. + * + * NOTE: Here we use a quick note lookup, but we could also + * check to see if r->filename starts with 'proxy:' + */ + if (apr_table_get(r->notes, "rewrite-proxy") && + (ptr2 = ap_strcasestr(r->filename, "unix:")) && + (ptr = ap_strchr(ptr2, '|'))) { + apr_uri_t urisock; + apr_status_t rv; + *ptr = '\0'; + rv = apr_uri_parse(r->pool, ptr2, &urisock); + if (rv == APR_SUCCESS) { + char *rurl = ptr+1; + char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path); + apr_table_setn(r->notes, "uds_path", sockpath); + *url = apr_pstrdup(r->pool, rurl); /* so we get the scheme for the uds */ + /* r->filename starts w/ "proxy:", so add after that */ + memmove(r->filename+6, rurl, strlen(rurl)+1); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "*: rewrite of url due to UDS(%s): %s (%s)", + sockpath, *url, r->filename); + } + else { + *ptr = '|'; + } + } } } } @@ -2097,29 +2134,6 @@ PROXY_DECLARE(int) ap_proxy_acquire_connection(const char *proxy_function, (*conn)->close = 0; (*conn)->inreslist = 0; - if (*worker->s->uds_path) { - if ((*conn)->uds_path == NULL) { - /* use (*conn)->pool instead of worker->cp->pool to match lifetime */ - (*conn)->uds_path = apr_pstrdup((*conn)->pool, worker->s->uds_path); - } - if ((*conn)->uds_path) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02545) - "%s: has determined UDS as %s", - proxy_function, (*conn)->uds_path); - } - else { - /* should never happen */ - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02546) - "%s: cannot determine UDS (%s)", - proxy_function, worker->s->uds_path); - - } - } - else { - (*conn)->uds_path = NULL; - } - - return OK; } @@ -2150,6 +2164,7 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, int server_port; apr_status_t err = APR_SUCCESS; apr_status_t uerr = APR_SUCCESS; + const char *uds_path; /* * Break up the URL to determine the host to connect to @@ -2190,85 +2205,117 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, * to check host and port on the conn and be careful about * spilling the cached addr from the worker. */ - if (!conn->hostname || !worker->s->is_address_reusable || - worker->s->disablereuse || *worker->s->uds_path) { - if (proxyname) { - conn->hostname = apr_pstrdup(conn->pool, proxyname); - conn->port = proxyport; - /* - * If we have a forward proxy and the protocol is HTTPS, - * then we need to prepend a HTTP CONNECT request before - * sending our actual HTTPS requests. - * Save our real backend data for using it later during HTTP CONNECT. - */ - if (conn->is_ssl) { - const char *proxy_auth; - - forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info)); - conn->forward = forward; - forward->use_http_connect = 1; - forward->target_host = apr_pstrdup(conn->pool, uri->hostname); - forward->target_port = uri->port; - /* Do we want to pass Proxy-Authorization along? - * If we haven't used it, then YES - * If we have used it then MAYBE: RFC2616 says we MAY propagate it. - * So let's make it configurable by env. - * The logic here is the same used in mod_proxy_http. - */ - proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization"); - if (proxy_auth != NULL && - proxy_auth[0] != '\0' && - r->user == NULL && /* we haven't yet authenticated */ - apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { - forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth); - } - } + uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path")); + if (uds_path) { + if (conn->uds_path == NULL) { + /* use (*conn)->pool instead of worker->cp->pool to match lifetime */ + conn->uds_path = apr_pstrdup(conn->pool, uds_path); } - else { - conn->hostname = apr_pstrdup(conn->pool, uri->hostname); - conn->port = uri->port; + if (conn->uds_path) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02545) + "%s: has determined UDS as %s", + uri->scheme, conn->uds_path); } - socket_cleanup(conn); - if (!(*worker->s->uds_path) && - (!worker->s->is_address_reusable || worker->s->disablereuse)) { - /* - * Only do a lookup if we should not reuse the backend address. - * Otherwise we will look it up once for the worker. - */ - err = apr_sockaddr_info_get(&(conn->addr), - conn->hostname, APR_UNSPEC, - conn->port, 0, - conn->pool); + else { + /* should never happen */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02546) + "%s: cannot determine UDS (%s)", + uri->scheme, uds_path); + } - } - if (!(*worker->s->uds_path) && worker->s->is_address_reusable && !worker->s->disablereuse) { /* - * Looking up the backend address for the worker only makes sense if - * we can reuse the address. + * In UDS cases, some structs are NULL. Protect from de-refs + * and provide info for logging at the same time. */ - if (!worker->cp->addr) { - if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock"); - return HTTP_INTERNAL_SERVER_ERROR; + if (!conn->addr) { + apr_sockaddr_t *sa; + apr_sockaddr_info_get(&sa, NULL, APR_UNSPEC, 0, 0, conn->pool); + conn->addr = sa; + } + conn->hostname = "httpd-UDS"; + conn->port = 0; + } + else { + int will_reuse = worker->s->is_address_reusable && !worker->s->disablereuse; + if (!conn->hostname || !will_reuse) { + if (proxyname) { + conn->hostname = apr_pstrdup(conn->pool, proxyname); + conn->port = proxyport; + /* + * If we have a forward proxy and the protocol is HTTPS, + * then we need to prepend a HTTP CONNECT request before + * sending our actual HTTPS requests. + * Save our real backend data for using it later during HTTP CONNECT. + */ + if (conn->is_ssl) { + const char *proxy_auth; + + forward_info *forward = apr_pcalloc(conn->pool, sizeof(forward_info)); + conn->forward = forward; + forward->use_http_connect = 1; + forward->target_host = apr_pstrdup(conn->pool, uri->hostname); + forward->target_port = uri->port; + /* Do we want to pass Proxy-Authorization along? + * If we haven't used it, then YES + * If we have used it then MAYBE: RFC2616 says we MAY propagate it. + * So let's make it configurable by env. + * The logic here is the same used in mod_proxy_http. + */ + proxy_auth = apr_table_get(r->headers_in, "Proxy-Authorization"); + if (proxy_auth != NULL && + proxy_auth[0] != '\0' && + r->user == NULL && /* we haven't yet authenticated */ + apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { + forward->proxy_auth = apr_pstrdup(conn->pool, proxy_auth); + } + } } - + else { + conn->hostname = apr_pstrdup(conn->pool, uri->hostname); + conn->port = uri->port; + } + if (!will_reuse) { + /* + * Only do a lookup if we should not reuse the backend address. + * Otherwise we will look it up once for the worker. + */ + err = apr_sockaddr_info_get(&(conn->addr), + conn->hostname, APR_UNSPEC, + conn->port, 0, + conn->pool); + } + socket_cleanup(conn); + conn->close = 0; + } + if (will_reuse) { /* - * Worker can have the single constant backend adress. - * The single DNS lookup is used once per worker. - * If dynamic change is needed then set the addr to NULL - * inside dynamic config to force the lookup. + * Looking up the backend address for the worker only makes sense if + * we can reuse the address. */ - err = apr_sockaddr_info_get(&(worker->cp->addr), - conn->hostname, APR_UNSPEC, - conn->port, 0, - worker->cp->pool); - conn->addr = worker->cp->addr; - if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock"); + if (!worker->cp->addr) { + if ((err = PROXY_THREAD_LOCK(worker)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, err, r, APLOGNO(00945) "lock"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* + * Worker can have the single constant backend adress. + * The single DNS lookup is used once per worker. + * If dynamic change is needed then set the addr to NULL + * inside dynamic config to force the lookup. + */ + err = apr_sockaddr_info_get(&(worker->cp->addr), + conn->hostname, APR_UNSPEC, + conn->port, 0, + worker->cp->pool); + conn->addr = worker->cp->addr; + if ((uerr = PROXY_THREAD_UNLOCK(worker)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, uerr, r, APLOGNO(00946) "unlock"); + } + } + else { + conn->addr = worker->cp->addr; } - } - else { - conn->addr = worker->cp->addr; } } /* Close a possible existing socket if we are told to do so */ @@ -2284,22 +2331,56 @@ ap_proxy_determine_connection(apr_pool_t *p, request_rec *r, } /* Get the server port for the Via headers */ - { - server_port = ap_get_server_port(r); - if (ap_is_default_port(server_port, r)) { - strcpy(server_portstr,""); - } - else { - apr_snprintf(server_portstr, server_portstr_size, ":%d", - server_port); - } + server_port = ap_get_server_port(r); + AP_DEBUG_ASSERT(server_portstr_size > 0); + if (ap_is_default_port(server_port, r)) { + server_portstr[0] = '\0'; } + else { + apr_snprintf(server_portstr, server_portstr_size, ":%d", + server_port); + } + /* check if ProxyBlock directive on this host */ if (OK != ap_proxy_checkproxyblock2(r, conf, uri->hostname, proxyname ? NULL : conn->addr)) { return ap_proxyerror(r, HTTP_FORBIDDEN, "Connect to remote machine blocked"); } + /* + * When SSL is configured, determine the hostname (SNI) for the request + * and save it in conn->ssl_hostname. Close any reused connection whose + * SNI differs. + */ + if (conn->is_ssl) { + proxy_dir_conf *dconf; + const char *ssl_hostname; + /* + * In the case of ProxyPreserveHost on use the hostname of + * the request if present otherwise use the one from the + * backend request URI. + */ + dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + if (dconf->preserve_host) { + ssl_hostname = r->hostname; + } + else { + ssl_hostname = conn->hostname; + } + /* + * Close if a SNI is in use but this request requires no or + * a different one, or no SNI is in use but one is required. + */ + if ((conn->ssl_hostname && (!ssl_hostname || + strcasecmp(conn->ssl_hostname, + ssl_hostname) != 0)) || + (!conn->ssl_hostname && ssl_hostname && conn->sock)) { + socket_cleanup(conn); + } + if (conn->ssl_hostname == NULL) { + conn->ssl_hostname = apr_pstrdup(conn->scpool, ssl_hostname); + } + } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00947) "connected %s to %s:%d", *url, conn->hostname, conn->port); return OK; @@ -3051,7 +3132,7 @@ static int find_conn_headers(void *data, const char *key, const char *val) const char *name; do { - while (*val == ',') { + while (*val == ',' || *val == ';') { val++; } name = ap_get_token(x->pool, &val, 0); @@ -3226,8 +3307,22 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, * to backend */ if (do_100_continue) { - apr_table_mergen(r->headers_in, "Expect", "100-Continue"); - r->expecting_100 = 1; + const char *val; + + if (!r->expecting_100) { + /* Don't forward any "100 Continue" response if the client is + * not expecting it. + */ + apr_table_setn(r->subprocess_env, "proxy-interim-response", + "Suppress"); + } + + /* Add the Expect header if not already there. */ + if (((val = apr_table_get(r->headers_in, "Expect")) == NULL) + || (strcasecmp(val, "100-Continue") != 0 /* fast path */ + && !ap_find_token(r->pool, val, "100-Continue"))) { + apr_table_mergen(r->headers_in, "Expect", "100-Continue"); + } } /* X-Forwarded-*: handling @@ -3408,6 +3503,7 @@ static proxy_schemes_t pschemes[] = { {"fcgi", 8000}, {"ajp", AJP13_DEF_PORT}, + {"scgi", SCGI_DEF_PORT}, { NULL, 0xFFFF } /* unknown port */ }; diff --git a/modules/proxy/scgi.h b/modules/proxy/scgi.h new file mode 100644 index 00000000..a2e5e965 --- /dev/null +++ b/modules/proxy/scgi.h @@ -0,0 +1,36 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file scgi.h + * @brief Shared SCGI-related definitions + * + * @ingroup APACHE_INTERNAL + * @{ + */ + +#ifndef SCGI_H +#define SCGI_H + +/* This is not defined by the protocol. It is a convention + * of mod_proxy_scgi, and mod_proxy utility routines must + * use the same value as mod_proxy_scgi. + */ +#define SCGI_DEF_PORT 4000 + +/** @} */ + +#endif /* SCGI_H */ |