| /* 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. | |
| */ | |
| #include "apr_hash.h" | |
| #include "apr_lib.h" | |
| #include "apr_strings.h" | |
| #include "ap_provider.h" | |
| #include "httpd.h" | |
| #include "http_config.h" | |
| #include "http_core.h" | |
| #include "http_protocol.h" | |
| #include "http_request.h" | |
| #include "http_log.h" | |
| #include "util_script.h" | |
| #include "ap_provider.h" | |
| #include "mod_auth.h" | |
| #include "util_fcgi.h" | |
| #include "ap_mmn.h" | |
| module AP_MODULE_DECLARE_DATA authnz_fcgi_module; | |
| typedef struct { | |
| const char *name; /* provider name */ | |
| const char *backend; /* backend address, as configured */ | |
| const char *host; | |
| apr_port_t port; | |
| apr_sockaddr_t *backend_addrs; | |
| int is_authn; | |
| int is_authz; | |
| } fcgi_provider_conf; | |
| typedef struct { | |
| const char *name; /* provider name */ | |
| const char *default_user; /* this is user if authorizer returns | |
| * success and a user expression yields | |
| * empty string | |
| */ | |
| ap_expr_info_t *user_expr; /* expr to evaluate to set r->user */ | |
| char authoritative; /* fail request if user is rejected? */ | |
| char require_basic_auth; /* fail if client didn't send credentials? */ | |
| } fcgi_dir_conf; | |
| typedef struct { | |
| /* If an "authnz" provider successfully authenticates, record | |
| * the provider name here for checking during authz. | |
| */ | |
| const char *successful_authnz_provider; | |
| } fcgi_request_notes; | |
| static apr_hash_t *fcgi_authn_providers, *fcgi_authz_providers; | |
| #define FCGI_IO_TIMEOUT apr_time_from_sec(30) | |
| #ifndef NON200_RESPONSE_BUF_LEN | |
| #define NON200_RESPONSE_BUF_LEN 8192 | |
| #endif | |
| /* fcgi://{hostname|IPv4|IPv6}:port[/] */ | |
| #define FCGI_BACKEND_REGEX_STR "m%^fcgi://(.*):(\\d{1,5})/?$%" | |
| /* | |
| * utility function to connect to a peer; generally useful, but | |
| * wait for AF_UNIX support in this mod before thinking about how | |
| * to make it available to other modules | |
| */ | |
| static apr_status_t connect_to_peer(apr_socket_t **newsock, | |
| request_rec *r, | |
| apr_sockaddr_t *backend_addrs, | |
| const char *backend_name, | |
| apr_interval_time_t timeout) | |
| { | |
| apr_status_t rv = APR_EINVAL; /* returned if no backend addr was provided | |
| */ | |
| int connected = 0; | |
| apr_sockaddr_t *addr = backend_addrs; | |
| while (addr && !connected) { | |
| int loglevel = addr->next ? APLOG_DEBUG : APLOG_ERR; | |
| rv = apr_socket_create(newsock, addr->family, | |
| SOCK_STREAM, 0, r->pool); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, loglevel, rv, r, | |
| APLOGNO(02494) "error creating family %d socket " | |
| "for target %s", | |
| addr->family, backend_name); | |
| addr = addr->next; | |
| continue; | |
| } | |
| apr_socket_opt_set(*newsock, APR_TCP_NODELAY, 1); | |
| apr_socket_timeout_set(*newsock, | |
| timeout ? timeout : r->server->timeout); | |
| rv = apr_socket_connect(*newsock, addr); | |
| if (rv != APR_SUCCESS) { | |
| apr_socket_close(*newsock); | |
| ap_log_rerror(APLOG_MARK, loglevel, rv, r, | |
| APLOGNO(02495) "attempt to connect to %pI (%s) " | |
| "failed", addr, backend_name); | |
| addr = addr->next; | |
| continue; | |
| } | |
| connected = 1; | |
| } | |
| return rv; | |
| #undef FN_LOG_MARK | |
| } | |
| static void log_provider_info(const fcgi_provider_conf *conf, request_rec *r) | |
| { | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02496) "name %s, backend %s, host %s, port %d, " | |
| "first address %pI, %c%c", | |
| conf->name, | |
| conf->backend, | |
| conf->host, | |
| (int)conf->port, | |
| conf->backend_addrs, | |
| conf->is_authn ? 'N' : '_', | |
| conf->is_authz ? 'Z' : '_'); | |
| } | |
| static void setupenv(request_rec *r, const char *password, const char *apache_role) | |
| { | |
| ap_add_common_vars(r); | |
| ap_add_cgi_vars(r); | |
| apr_table_setn(r->subprocess_env, "FCGI_ROLE", AP_FCGI_AUTHORIZER_STR); | |
| if (apache_role) { | |
| apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", apache_role); | |
| } | |
| if (password) { | |
| apr_table_setn(r->subprocess_env, "REMOTE_PASSWD", password); | |
| } | |
| /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED, | |
| * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass | |
| * PROXY_AUTH to allow CGI to perform proxy auth for httpd | |
| */ | |
| apr_table_unset(r->subprocess_env, "CONTENT_LENGTH"); | |
| apr_table_unset(r->subprocess_env, "PATH_INFO"); | |
| apr_table_unset(r->subprocess_env, "PATH_TRANSLATED"); | |
| apr_table_unset(r->subprocess_env, "SCRIPT_NAME"); | |
| apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE"); | |
| apr_table_unset(r->subprocess_env, "HTTP_TE"); | |
| apr_table_unset(r->subprocess_env, "HTTP_TRAILER"); | |
| apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING"); | |
| apr_table_unset(r->subprocess_env, "HTTP_UPGRADE"); | |
| /* Connection hop-by-hop header to prevent the CGI from hanging */ | |
| apr_table_setn(r->subprocess_env, "HTTP_CONNECTION", "close"); | |
| } | |
| static apr_status_t recv_data(const fcgi_provider_conf *conf, | |
| request_rec *r, | |
| apr_socket_t *s, | |
| char *buf, | |
| apr_size_t *buflen) | |
| { | |
| apr_status_t rv; | |
| rv = apr_socket_recv(s, buf, buflen); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02497) "Couldn't read from backend %s", | |
| conf->backend); | |
| return rv; | |
| } | |
| #if AP_MODULE_MAGIC_AT_LEAST(20130702,2) | |
| ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data received", | |
| buf, *buflen, AP_LOG_DATA_SHOW_OFFSET); | |
| #endif | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t recv_data_full(const fcgi_provider_conf *conf, | |
| request_rec *r, | |
| apr_socket_t *s, | |
| char *buf, | |
| apr_size_t buflen) | |
| { | |
| apr_size_t readlen; | |
| apr_size_t cumulative_len = 0; | |
| apr_status_t rv; | |
| do { | |
| readlen = buflen - cumulative_len; | |
| rv = recv_data(conf, r, s, buf + cumulative_len, &readlen); | |
| if (rv != APR_SUCCESS) { | |
| return rv; | |
| } | |
| cumulative_len += readlen; | |
| } while (cumulative_len < buflen); | |
| return APR_SUCCESS; | |
| } | |
| static apr_status_t sendv_data(const fcgi_provider_conf *conf, | |
| request_rec *r, | |
| apr_socket_t *s, | |
| struct iovec *vec, | |
| int nvec, | |
| apr_size_t *len) | |
| { | |
| apr_size_t to_write = 0, written = 0; | |
| apr_status_t rv = APR_SUCCESS; | |
| int i, offset; | |
| for (i = 0; i < nvec; i++) { | |
| to_write += vec[i].iov_len; | |
| #if AP_MODULE_MAGIC_AT_LEAST(20130702,2) | |
| ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data sent", | |
| vec[i].iov_base, vec[i].iov_len, AP_LOG_DATA_SHOW_OFFSET); | |
| #endif | |
| } | |
| offset = 0; | |
| while (to_write) { | |
| apr_size_t n = 0; | |
| rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02498) "Sending data to %s failed", | |
| conf->backend); | |
| break; | |
| } | |
| if (n > 0) { | |
| written += n; | |
| if (written >= to_write) | |
| break; /* short circuit out */ | |
| for (i = offset; i < nvec; ) { | |
| if (n >= vec[i].iov_len) { | |
| offset++; | |
| n -= vec[i++].iov_len; | |
| } else { | |
| vec[i].iov_len -= n; | |
| vec[i].iov_base = (char *) vec[i].iov_base + n; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| *len = written; | |
| return rv; | |
| } | |
| static apr_status_t send_begin_request(request_rec *r, | |
| const fcgi_provider_conf *conf, | |
| apr_socket_t *s, int role, | |
| apr_uint16_t request_id) | |
| { | |
| struct iovec vec[2]; | |
| ap_fcgi_header header; | |
| unsigned char farray[AP_FCGI_HEADER_LEN]; | |
| ap_fcgi_begin_request_body brb; | |
| unsigned char abrb[AP_FCGI_HEADER_LEN]; | |
| apr_size_t len; | |
| ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id, | |
| sizeof(abrb), 0); | |
| ap_fcgi_fill_in_request_body(&brb, role, 0 /* *NOT* AP_FCGI_KEEP_CONN */); | |
| ap_fcgi_header_to_array(&header, farray); | |
| ap_fcgi_begin_request_body_to_array(&brb, abrb); | |
| vec[0].iov_base = (void *)farray; | |
| vec[0].iov_len = sizeof(farray); | |
| vec[1].iov_base = (void *)abrb; | |
| vec[1].iov_len = sizeof(abrb); | |
| return sendv_data(conf, r, s, vec, 2, &len); | |
| } | |
| static apr_status_t send_environment(apr_socket_t *s, | |
| const fcgi_provider_conf *conf, | |
| request_rec *r, apr_uint16_t request_id, | |
| apr_pool_t *temp_pool) | |
| { | |
| const char *fn = "send_environment"; | |
| const apr_array_header_t *envarr; | |
| const apr_table_entry_t *elts; | |
| struct iovec vec[2]; | |
| ap_fcgi_header header; | |
| unsigned char farray[AP_FCGI_HEADER_LEN]; | |
| char *body; | |
| apr_status_t rv; | |
| apr_size_t avail_len, len, required_len; | |
| int i, next_elem, starting_elem; | |
| envarr = apr_table_elts(r->subprocess_env); | |
| elts = (const apr_table_entry_t *) envarr->elts; | |
| if (APLOG_R_IS_LEVEL(r, APLOG_TRACE2)) { | |
| for (i = 0; i < envarr->nelts; ++i) { | |
| if (!elts[i].key) { | |
| continue; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, | |
| "%s: '%s': '%s'", | |
| fn, elts[i].key, | |
| !strcmp(elts[i].key, "REMOTE_PASSWD") ? | |
| "XXXXXXXX" : elts[i].val); | |
| } | |
| } | |
| /* Send envvars over in as many FastCGI records as it takes, */ | |
| next_elem = 0; /* starting with the first one */ | |
| avail_len = 16 * 1024; /* our limit per record, which could have been up | |
| * to AP_FCGI_MAX_CONTENT_LEN | |
| */ | |
| while (next_elem < envarr->nelts) { | |
| starting_elem = next_elem; | |
| required_len = ap_fcgi_encoded_env_len(r->subprocess_env, | |
| avail_len, | |
| &next_elem); | |
| if (!required_len) { | |
| if (next_elem < envarr->nelts) { | |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
| APLOGNO(02499) "couldn't encode envvar '%s' in %" | |
| APR_SIZE_T_FMT " bytes", | |
| elts[next_elem].key, avail_len); | |
| /* skip this envvar and continue */ | |
| ++next_elem; | |
| continue; | |
| } | |
| /* only an unused element at the end of the array */ | |
| break; | |
| } | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02500) "required len for encoding envvars: %" | |
| APR_SIZE_T_FMT ", %d/%d elems processed so far", | |
| required_len, next_elem, envarr->nelts); | |
| body = apr_palloc(temp_pool, required_len); | |
| rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len, | |
| &starting_elem); | |
| /* we pre-compute, so we can't run out of space */ | |
| ap_assert(rv == APR_SUCCESS); | |
| /* compute and encode must be in sync */ | |
| ap_assert(starting_elem == next_elem); | |
| ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, | |
| (apr_uint16_t)required_len, 0); | |
| ap_fcgi_header_to_array(&header, farray); | |
| vec[0].iov_base = (void *)farray; | |
| vec[0].iov_len = sizeof(farray); | |
| vec[1].iov_base = body; | |
| vec[1].iov_len = required_len; | |
| rv = sendv_data(conf, r, s, vec, 2, &len); | |
| apr_pool_clear(temp_pool); | |
| if (rv) { | |
| return rv; | |
| } | |
| } | |
| /* Envvars sent, so say we're done */ | |
| ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0); | |
| ap_fcgi_header_to_array(&header, farray); | |
| vec[0].iov_base = (void *)farray; | |
| vec[0].iov_len = sizeof(farray); | |
| return sendv_data(conf, r, s, vec, 1, &len); | |
| } | |
| /* | |
| * This header-state logic is from mod_proxy_fcgi. | |
| */ | |
| enum { | |
| HDR_STATE_READING_HEADERS, | |
| HDR_STATE_GOT_CR, | |
| HDR_STATE_GOT_CRLF, | |
| HDR_STATE_GOT_CRLFCR, | |
| HDR_STATE_GOT_LF, | |
| HDR_STATE_DONE_WITH_HEADERS | |
| }; | |
| /* Try to find the end of the script headers in the response from the back | |
| * end fastcgi server. STATE holds the current header parsing state for this | |
| * request. | |
| * | |
| * Returns 0 if it can't find the end of the headers, and 1 if it found the | |
| * end of the headers. */ | |
| static int handle_headers(request_rec *r, int *state, | |
| const char *readbuf, apr_size_t readlen) | |
| { | |
| const char *itr = readbuf; | |
| while (readlen--) { | |
| if (*itr == '\r') { | |
| switch (*state) { | |
| case HDR_STATE_GOT_CRLF: | |
| *state = HDR_STATE_GOT_CRLFCR; | |
| break; | |
| default: | |
| *state = HDR_STATE_GOT_CR; | |
| break; | |
| } | |
| } | |
| else if (*itr == '\n') { | |
| switch (*state) { | |
| case HDR_STATE_GOT_LF: | |
| *state = HDR_STATE_DONE_WITH_HEADERS; | |
| break; | |
| case HDR_STATE_GOT_CR: | |
| *state = HDR_STATE_GOT_CRLF; | |
| break; | |
| case HDR_STATE_GOT_CRLFCR: | |
| *state = HDR_STATE_DONE_WITH_HEADERS; | |
| break; | |
| default: | |
| *state = HDR_STATE_GOT_LF; | |
| break; | |
| } | |
| } | |
| else { | |
| *state = HDR_STATE_READING_HEADERS; | |
| } | |
| if (*state == HDR_STATE_DONE_WITH_HEADERS) | |
| break; | |
| ++itr; | |
| } | |
| if (*state == HDR_STATE_DONE_WITH_HEADERS) { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * handle_response() is based on mod_proxy_fcgi's dispatch() | |
| */ | |
| static apr_status_t handle_response(const fcgi_provider_conf *conf, | |
| request_rec *r, apr_socket_t *s, | |
| apr_pool_t *temp_pool, | |
| apr_uint16_t request_id, | |
| char *rspbuf, | |
| apr_size_t *rspbuflen) | |
| { | |
| apr_bucket *b; | |
| apr_bucket_brigade *ob; | |
| apr_size_t orspbuflen = 0; | |
| apr_status_t rv = APR_SUCCESS; | |
| const char *fn = "handle_response"; | |
| int header_state = HDR_STATE_READING_HEADERS; | |
| int seen_end_of_headers = 0, done = 0; | |
| if (rspbuflen) { | |
| orspbuflen = *rspbuflen; | |
| *rspbuflen = 0; /* unless we actually read something */ | |
| } | |
| ob = apr_brigade_create(r->pool, r->connection->bucket_alloc); | |
| while (!done && rv == APR_SUCCESS) { /* Keep reading FastCGI records until | |
| * we get AP_FCGI_END_REQUEST (done) | |
| * or an error occurs. | |
| */ | |
| apr_size_t readbuflen; | |
| apr_uint16_t clen; | |
| apr_uint16_t rid; | |
| char readbuf[AP_IOBUFSIZE]; | |
| unsigned char farray[AP_FCGI_HEADER_LEN]; | |
| unsigned char plen; | |
| unsigned char type; | |
| unsigned char version; | |
| rv = recv_data_full(conf, r, s, (char *)farray, AP_FCGI_HEADER_LEN); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02501) "%s: Error occurred before reading " | |
| "entire header", fn); | |
| break; | |
| } | |
| ap_fcgi_header_fields_from_array(&version, &type, &rid, &clen, &plen, | |
| farray); | |
| if (version != AP_FCGI_VERSION_1) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02502) "%s: Got bogus FastCGI header " | |
| "version %d", fn, (int)version); | |
| rv = APR_EINVAL; | |
| break; | |
| } | |
| if (rid != request_id) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02503) "%s: Got bogus FastCGI header " | |
| "request id %d, expected %d", | |
| fn, rid, request_id); | |
| rv = APR_EINVAL; | |
| break; | |
| } | |
| recv_again: /* if we need to keep reading more of a record's content */ | |
| if (clen > sizeof(readbuf)) { | |
| readbuflen = sizeof(readbuf); | |
| } else { | |
| readbuflen = clen; | |
| } | |
| /* | |
| * Now get the actual content of the record. | |
| */ | |
| if (readbuflen != 0) { | |
| rv = recv_data(conf, r, s, readbuf, &readbuflen); | |
| if (rv != APR_SUCCESS) { | |
| break; | |
| } | |
| } | |
| switch (type) { | |
| case AP_FCGI_STDOUT: /* Response headers and optional body */ | |
| if (clen != 0) { | |
| b = apr_bucket_transient_create(readbuf, | |
| readbuflen, | |
| r->connection->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(ob, b); | |
| if (!seen_end_of_headers) { | |
| int st = handle_headers(r, &header_state, | |
| readbuf, readbuflen); | |
| if (st == 1) { | |
| int status; | |
| seen_end_of_headers = 1; | |
| status = | |
| ap_scan_script_header_err_brigade_ex(r, ob, | |
| NULL, | |
| APLOG_MODULE_INDEX); | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02504) "%s: script header " | |
| "parsing -> %d/%d", | |
| fn, status, r->status); | |
| /* FCGI has its own body framing mechanism which we don't | |
| * match against any provided Content-Length, so let the | |
| * core determine C-L vs T-E based on what's actually sent. | |
| */ | |
| if (!apr_table_get(r->subprocess_env, AP_TRUST_CGILIKE_CL_ENVVAR)) | |
| apr_table_unset(r->headers_out, "Content-Length"); | |
| apr_table_unset(r->headers_out, "Transfer-Encoding"); | |
| if (rspbuf) { /* caller wants to see response body, | |
| * if any | |
| */ | |
| apr_status_t tmprv; | |
| if (rspbuflen) { | |
| *rspbuflen = orspbuflen; | |
| } | |
| tmprv = apr_brigade_flatten(ob, rspbuf, rspbuflen); | |
| if (tmprv != APR_SUCCESS) { | |
| /* should not occur for these bucket types; | |
| * does not indicate overflow | |
| */ | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, tmprv, r, | |
| APLOGNO(02505) "%s: error " | |
| "flattening response body", | |
| fn); | |
| } | |
| } | |
| if (status != OK) { | |
| r->status = status; | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02506) "%s: Error parsing " | |
| "script headers from %s", | |
| fn, conf->backend); | |
| rv = APR_EINVAL; | |
| break; | |
| } | |
| apr_pool_clear(temp_pool); | |
| } | |
| else { | |
| /* We're still looking for the end of the | |
| * headers, so this part of the data will need | |
| * to persist. */ | |
| apr_bucket_setaside(b, temp_pool); | |
| } | |
| } | |
| /* If we didn't read all the data go back and get the | |
| * rest of it. */ | |
| if (clen > readbuflen) { | |
| clen -= readbuflen; | |
| goto recv_again; | |
| } | |
| } | |
| break; | |
| case AP_FCGI_STDERR: /* Text to log */ | |
| if (clen) { | |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
| APLOGNO(02507) "%s: Logged from %s: '%.*s'", | |
| fn, conf->backend, (int)readbuflen, readbuf); | |
| } | |
| if (clen > readbuflen) { | |
| clen -= readbuflen; | |
| goto recv_again; /* continue reading this record */ | |
| } | |
| break; | |
| case AP_FCGI_END_REQUEST: | |
| done = 1; | |
| break; | |
| default: | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02508) "%s: Got bogus FastCGI record type " | |
| "%d", fn, type); | |
| break; | |
| } | |
| /* Leave on above switch's inner error. */ | |
| if (rv != APR_SUCCESS) { | |
| break; | |
| } | |
| /* | |
| * Read/discard any trailing padding. | |
| */ | |
| if (plen) { | |
| rv = recv_data_full(conf, r, s, readbuf, plen); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02509) "%s: Error occurred reading " | |
| "padding", | |
| fn); | |
| break; | |
| } | |
| } | |
| } | |
| apr_brigade_cleanup(ob); | |
| if (rv == APR_SUCCESS && !seen_end_of_headers) { | |
| rv = APR_EINVAL; | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02510) "%s: Never reached end of script headers", | |
| fn); | |
| } | |
| return rv; | |
| } | |
| /* almost from mod_fcgid */ | |
| static int mod_fcgid_modify_auth_header(void *vars, | |
| const char *key, const char *val) | |
| { | |
| /* When the application gives a 200 response, the server ignores response | |
| headers whose names aren't prefixed with Variable- prefix, and ignores | |
| any response content */ | |
| if (ap_cstr_casecmpn(key, "Variable-", 9) == 0) | |
| apr_table_setn(vars, key, val); | |
| return 1; | |
| } | |
| static int fix_auth_header(void *vr, const char *key, const char *val) | |
| { | |
| request_rec *r = vr; | |
| ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "moving %s->%s", key, val); | |
| apr_table_unset(r->err_headers_out, key); | |
| apr_table_setn(r->subprocess_env, key + 9, val); | |
| return 1; | |
| } | |
| static void req_rsp(request_rec *r, const fcgi_provider_conf *conf, | |
| const char *password, const char *apache_role, | |
| char *rspbuf, apr_size_t *rspbuflen) | |
| { | |
| const char *fn = "req_rsp"; | |
| apr_pool_t *temp_pool; | |
| apr_size_t orspbuflen = 0; | |
| apr_socket_t *s; | |
| apr_status_t rv; | |
| apr_table_t *saved_subprocess_env = | |
| apr_table_copy(r->pool, r->subprocess_env); | |
| if (rspbuflen) { | |
| orspbuflen = *rspbuflen; | |
| *rspbuflen = 0; /* unless we actually read something */ | |
| } | |
| apr_pool_create(&temp_pool, r->pool); | |
| apr_pool_tag(temp_pool, "mod_authnz_fcgi (req_rsp)"); | |
| setupenv(r, password, apache_role); | |
| rv = connect_to_peer(&s, r, conf->backend_addrs, | |
| conf->backend, FCGI_IO_TIMEOUT); | |
| if (rv == APR_SUCCESS) { | |
| apr_uint16_t request_id = 1; | |
| rv = send_begin_request(r, conf, s, AP_FCGI_AUTHORIZER, request_id); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02511) "%s: Failed writing request to %s", | |
| fn, conf->backend); | |
| } | |
| if (rv == APR_SUCCESS) { | |
| rv = send_environment(s, conf, r, request_id, temp_pool); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02512) "%s: Failed writing environment " | |
| "to %s", fn, conf->backend); | |
| } | |
| } | |
| /* The responder owns the request body, not the authorizer. | |
| * Don't even send an empty AP_FCGI_STDIN block. libfcgi doesn't care, | |
| * but it wasn't sent to authorizers by mod_fastcgi or mod_fcgi and | |
| * may be unhandled by the app. Additionally, the FastCGI spec does | |
| * not mention FCGI_STDIN in the Authorizer description, though it | |
| * does describe FCGI_STDIN elsewhere in more general terms than | |
| * simply a wrapper for the client's request body. | |
| */ | |
| if (rv == APR_SUCCESS) { | |
| if (rspbuflen) { | |
| *rspbuflen = orspbuflen; | |
| } | |
| rv = handle_response(conf, r, s, temp_pool, request_id, rspbuf, | |
| rspbuflen); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
| APLOGNO(02514) "%s: Failed handling response " | |
| "from %s", fn, conf->backend); | |
| } | |
| } | |
| apr_socket_close(s); | |
| } | |
| if (rv != APR_SUCCESS) { | |
| /* some sort of mechanical problem */ | |
| r->status = HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| else { | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02515) "%s: Received HTTP status %d", | |
| fn, r->status); | |
| } | |
| r->subprocess_env = saved_subprocess_env; | |
| if (r->status == HTTP_OK) { | |
| /* An Authorizer application's 200 response may include headers | |
| * whose names are prefixed with Variable-, and they should be | |
| * available to subsequent phases via subprocess_env (and yanked | |
| * from the client response). | |
| */ | |
| apr_table_t *vars = apr_table_make(temp_pool, /* not used to allocate | |
| * any values that end up | |
| * in r->(anything) | |
| */ | |
| 10); | |
| apr_table_do(mod_fcgid_modify_auth_header, vars, | |
| r->err_headers_out, NULL); | |
| apr_table_do(fix_auth_header, r, vars, NULL); | |
| } | |
| apr_pool_destroy(temp_pool); | |
| } | |
| static int fcgi_check_authn(request_rec *r) | |
| { | |
| const char *fn = "fcgi_check_authn"; | |
| fcgi_dir_conf *dconf = ap_get_module_config(r->per_dir_config, | |
| &authnz_fcgi_module); | |
| const char *password = NULL; | |
| const fcgi_provider_conf *conf; | |
| const char *prov; | |
| const char *auth_type; | |
| char rspbuf[NON200_RESPONSE_BUF_LEN + 1]; /* extra byte for '\0' */ | |
| apr_size_t rspbuflen = sizeof rspbuf - 1; | |
| int res; | |
| prov = dconf && dconf->name ? dconf->name : NULL; | |
| if (!prov || !ap_cstr_casecmp(prov, "None")) { | |
| return DECLINED; | |
| } | |
| auth_type = ap_auth_type(r); | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02516) "%s, prov %s, authoritative %s, " | |
| "require-basic %s, user expr? %s type %s", | |
| fn, prov, | |
| dconf->authoritative ? "yes" : "no", | |
| dconf->require_basic_auth ? "yes" : "no", | |
| dconf->user_expr ? "yes" : "no", | |
| auth_type); | |
| if (auth_type && !ap_cstr_casecmp(auth_type, "Basic")) { | |
| if ((res = ap_get_basic_auth_pw(r, &password))) { | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02517) "%s: couldn't retrieve basic auth " | |
| "password", fn); | |
| if (dconf->require_basic_auth) { | |
| return res; | |
| } | |
| password = NULL; | |
| } | |
| } | |
| conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING); | |
| if (!conf) { | |
| ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, | |
| APLOGNO(02518) "%s: can't find config for provider %s", | |
| fn, prov); | |
| return HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| if (APLOGrdebug(r)) { | |
| log_provider_info(conf, r); | |
| } | |
| req_rsp(r, conf, password, AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR, | |
| rspbuf, &rspbuflen); | |
| if (r->status == HTTP_OK) { | |
| if (dconf->user_expr) { | |
| const char *err; | |
| const char *user = ap_expr_str_exec(r, dconf->user_expr, | |
| &err); | |
| if (user && *user) { | |
| r->user = apr_pstrdup(r->pool, user); | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02519) "%s: Setting user to '%s'", | |
| fn, r->user); | |
| } | |
| else if (user && dconf->default_user) { | |
| r->user = apr_pstrdup(r->pool, dconf->default_user); | |
| } | |
| else if (user) { | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02520) "%s: Failure extracting user " | |
| "after calling authorizer: user expression " | |
| "yielded empty string (variable not set?)", | |
| fn); | |
| r->status = HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| else { | |
| /* unexpected error, not even an empty string was returned */ | |
| ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
| APLOGNO(02521) "%s: Failure extracting user " | |
| "after calling authorizer: %s", | |
| fn, err); | |
| r->status = HTTP_INTERNAL_SERVER_ERROR; | |
| } | |
| } | |
| if (conf->is_authz) { | |
| /* combined authn/authz phase, so app won't be invoked for authz | |
| * | |
| * Remember that the request was successfully authorized by this | |
| * provider. | |
| */ | |
| fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes)); | |
| rnotes->successful_authnz_provider = conf->name; | |
| ap_set_module_config(r->request_config, &authnz_fcgi_module, | |
| rnotes); | |
| } | |
| } | |
| else { | |
| /* From the spec: | |
| * For Authorizer response status values other than "200" (OK), the | |
| * Web server denies access and sends the response status, headers, | |
| * and content back to the HTTP client. | |
| * But: | |
| * This only makes sense if this authorizer is authoritative. | |
| */ | |
| if (rspbuflen > 0 && !dconf->authoritative) { | |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
| APLOGNO(02522) "%s: Ignoring response body from non-" | |
| "authoritative authorizer", fn); | |
| } | |
| else if (rspbuflen > 0) { | |
| if (rspbuflen == sizeof rspbuf - 1) { | |
| /* apr_brigade_flatten() interface :( */ | |
| ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
| APLOGNO(02523) "%s: possible overflow handling " | |
| "response body", fn); | |
| } | |
| rspbuf[rspbuflen] = '\0'; /* we reserved an extra byte for '\0' */ | |
| ap_custom_response(r, r->status, rspbuf); /* API makes a copy */ | |
| } | |
| } | |
| return r->status == HTTP_OK ? | |
| OK : dconf->authoritative ? r->status : DECLINED; | |
| } | |
| static authn_status fcgi_check_password(request_rec *r, const char *user, | |
| const char *password) | |
| { | |
| const char *fn = "fcgi_check_password"; | |
| const char *prov = apr_table_get(r->notes, AUTHN_PROVIDER_NAME_NOTE); | |
| const fcgi_provider_conf *conf; | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02524) "%s(%s, XXX): provider %s", | |
| fn, user, prov); | |
| if (!prov) { | |
| ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, | |
| APLOGNO(02525) "%s: provider note isn't set", fn); | |
| return AUTH_GENERAL_ERROR; | |
| } | |
| conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING); | |
| if (!conf) { | |
| ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, | |
| APLOGNO(02526) "%s: can't find config for provider %s", | |
| fn, prov); | |
| return AUTH_GENERAL_ERROR; | |
| } | |
| if (APLOGrdebug(r)) { | |
| log_provider_info(conf, r); | |
| } | |
| req_rsp(r, conf, password, | |
| /* combined authn and authz: FCGI_APACHE_ROLE not set */ | |
| conf->is_authz ? NULL : AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR, | |
| NULL, NULL); | |
| if (r->status == HTTP_OK) { | |
| if (conf->is_authz) { | |
| /* combined authn/authz phase, so app won't be invoked for authz | |
| * | |
| * Remember that the request was successfully authorized by this | |
| * provider. | |
| */ | |
| fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes)); | |
| rnotes->successful_authnz_provider = conf->name; | |
| ap_set_module_config(r->request_config, &authnz_fcgi_module, | |
| rnotes); | |
| } | |
| return AUTH_GRANTED; | |
| } | |
| else if (r->status == HTTP_INTERNAL_SERVER_ERROR) { | |
| return AUTH_GENERAL_ERROR; | |
| } | |
| else { | |
| return AUTH_DENIED; | |
| } | |
| } | |
| static const authn_provider fcgi_authn_provider = { | |
| &fcgi_check_password, | |
| NULL /* get-realm-hash not supported */ | |
| }; | |
| static authz_status fcgi_authz_check(request_rec *r, | |
| const char *require_line, | |
| const void *parsed_require_line) | |
| { | |
| const char *fn = "fcgi_authz_check"; | |
| const char *prov = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE); | |
| const fcgi_provider_conf *conf; | |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, | |
| APLOGNO(02527) "%s(%s)", fn, require_line); | |
| if (!prov) { | |
| ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, | |
| APLOGNO(02528) "%s: provider note isn't set", fn); | |
| return AUTHZ_GENERAL_ERROR; | |
| } | |
| conf = apr_hash_get(fcgi_authz_providers, prov, APR_HASH_KEY_STRING); | |
| if (!conf) { | |
| ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, | |
| APLOGNO(02529) "%s: can't find config for provider %s", | |
| fn, prov); | |
| return AUTHZ_GENERAL_ERROR; | |
| } | |
| if (APLOGrdebug(r)) { | |
| log_provider_info(conf, r); | |
| } | |
| if (!r->user) { | |
| return AUTHZ_DENIED_NO_USER; | |
| } | |
| if (conf->is_authn) { | |
| /* combined authn/authz phase, so app won't be invoked for authz | |
| * | |
| * If the provider already successfully authorized this request, | |
| * success. | |
| */ | |
| fcgi_request_notes *rnotes = ap_get_module_config(r->request_config, | |
| &authnz_fcgi_module); | |
| if (rnotes | |
| && rnotes->successful_authnz_provider | |
| && !strcmp(rnotes->successful_authnz_provider, conf->name)) { | |
| return AUTHZ_GRANTED; | |
| } | |
| else { | |
| return AUTHZ_DENIED; | |
| } | |
| } | |
| else { | |
| req_rsp(r, conf, NULL, AP_FCGI_APACHE_ROLE_AUTHORIZER_STR, NULL, NULL); | |
| if (r->status == HTTP_OK) { | |
| return AUTHZ_GRANTED; | |
| } | |
| else if (r->status == HTTP_INTERNAL_SERVER_ERROR) { | |
| return AUTHZ_GENERAL_ERROR; | |
| } | |
| else { | |
| return AUTHZ_DENIED; | |
| } | |
| } | |
| } | |
| static const char *fcgi_authz_parse(cmd_parms *cmd, const char *require_line, | |
| const void **parsed_require_line) | |
| { | |
| /* Allowed form: Require [not] registered-provider-name<EOS> | |
| */ | |
| if (strcmp(require_line, "")) { | |
| return "mod_authnz_fcgi doesn't support restrictions on providers " | |
| "(i.e., multiple require args)"; | |
| } | |
| return NULL; | |
| } | |
| static const authz_provider fcgi_authz_provider = { | |
| &fcgi_authz_check, | |
| &fcgi_authz_parse, | |
| }; | |
| static const char *fcgi_check_authn_provider(cmd_parms *cmd, | |
| void *d, | |
| int argc, | |
| char *const argv[]) | |
| { | |
| const char *dname = "AuthnzFcgiCheckAuthnProvider"; | |
| fcgi_dir_conf *dc = d; | |
| int ca = 0; | |
| if (ca >= argc) { | |
| return apr_pstrcat(cmd->pool, dname, ": No provider given", NULL); | |
| } | |
| dc->name = argv[ca]; | |
| ca++; | |
| if (!strcasecmp(dc->name, "None")) { | |
| if (ca < argc) { | |
| return "Options aren't supported with \"None\""; | |
| } | |
| } | |
| while (ca < argc) { | |
| const char *var = argv[ca], *val; | |
| int badarg = 0; | |
| ca++; | |
| /* at present, everything needs an argument */ | |
| if (ca >= argc) { | |
| return apr_pstrcat(cmd->pool, dname, ": ", var, | |
| "needs an argument", NULL); | |
| } | |
| val = argv[ca]; | |
| ca++; | |
| if (!strcasecmp(var, "Authoritative")) { | |
| if (!strcasecmp(val, "On")) { | |
| dc->authoritative = 1; | |
| } | |
| else if (!strcasecmp(val, "Off")) { | |
| dc->authoritative = 0; | |
| } | |
| else { | |
| badarg = 1; | |
| } | |
| } | |
| else if (!strcasecmp(var, "DefaultUser")) { | |
| dc->default_user = val; | |
| } | |
| else if (!strcasecmp(var, "RequireBasicAuth")) { | |
| if (!strcasecmp(val, "On")) { | |
| dc->require_basic_auth = 1; | |
| } | |
| else if (!strcasecmp(val, "Off")) { | |
| dc->require_basic_auth = 0; | |
| } | |
| else { | |
| badarg = 1; | |
| } | |
| } | |
| else if (!strcasecmp(var, "UserExpr")) { | |
| const char *err; | |
| int flags = AP_EXPR_FLAG_DONT_VARY | AP_EXPR_FLAG_RESTRICTED | |
| | AP_EXPR_FLAG_STRING_RESULT; | |
| dc->user_expr = ap_expr_parse_cmd(cmd, val, | |
| flags, &err, NULL); | |
| if (err) { | |
| return apr_psprintf(cmd->pool, "%s: Error parsing '%s': '%s'", | |
| dname, val, err); | |
| } | |
| } | |
| else { | |
| return apr_pstrcat(cmd->pool, dname, ": Unexpected option '", | |
| var, "'", NULL); | |
| } | |
| if (badarg) { | |
| return apr_pstrcat(cmd->pool, dname, ": Bad argument '", | |
| val, "' to option '", var, "'", NULL); | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* AuthnzFcgiAuthDefineProvider {authn|authz|authnz} provider-name \ | |
| * fcgi://backendhost:backendport/ | |
| */ | |
| static const char *fcgi_define_provider(cmd_parms *cmd, | |
| void *d, | |
| int argc, | |
| char *const argv[]) | |
| { | |
| const char *dname = "AuthnzFcgiDefineProvider"; | |
| ap_rxplus_t *fcgi_backend_regex; | |
| apr_status_t rv; | |
| char *host; | |
| const char *err, *stype; | |
| fcgi_provider_conf *conf = apr_pcalloc(cmd->pool, sizeof(*conf)); | |
| int ca = 0, rc, port; | |
| fcgi_backend_regex = ap_rxplus_compile(cmd->pool, FCGI_BACKEND_REGEX_STR); | |
| if (!fcgi_backend_regex) { | |
| return apr_psprintf(cmd->pool, | |
| "%s: failed to compile regexec '%s'", | |
| dname, FCGI_BACKEND_REGEX_STR); | |
| } | |
| err = ap_check_cmd_context(cmd, GLOBAL_ONLY); | |
| if (err) | |
| return err; | |
| if (ca >= argc) { | |
| return apr_pstrcat(cmd->pool, dname, ": No type given", NULL); | |
| } | |
| stype = argv[ca]; | |
| ca++; | |
| if (!strcasecmp(stype, "authn")) { | |
| conf->is_authn = 1; | |
| } | |
| else if (!strcasecmp(stype, "authz")) { | |
| conf->is_authz = 1; | |
| } | |
| else if (!strcasecmp(stype, "authnz")) { | |
| conf->is_authn = conf->is_authz = 1; | |
| } | |
| else { | |
| return apr_pstrcat(cmd->pool, | |
| dname, | |
| ": Invalid provider type ", | |
| stype, | |
| NULL); | |
| } | |
| if (ca >= argc) { | |
| return apr_pstrcat(cmd->pool, dname, ": No provider name given", NULL); | |
| } | |
| conf->name = argv[ca]; | |
| ca++; | |
| if (ca >= argc) { | |
| return apr_pstrcat(cmd->pool, dname, ": No backend-address given", | |
| NULL); | |
| } | |
| rc = ap_rxplus_exec(cmd->pool, fcgi_backend_regex, argv[ca], NULL); | |
| if (!rc || ap_rxplus_nmatch(fcgi_backend_regex) != 3) { | |
| return apr_pstrcat(cmd->pool, | |
| dname, ": backend-address '", | |
| argv[ca], | |
| "' has invalid form", | |
| NULL); | |
| } | |
| host = ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 1); | |
| if (host[0] == '[' && host[strlen(host) - 1] == ']') { | |
| host += 1; | |
| host[strlen(host) - 1] = '\0'; | |
| } | |
| port = atoi(ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 2)); | |
| if (port > 65535) { | |
| return apr_pstrcat(cmd->pool, | |
| dname, ": backend-address '", | |
| argv[ca], | |
| "' has invalid port", | |
| NULL); | |
| } | |
| conf->backend = argv[ca]; | |
| conf->host = host; | |
| conf->port = port; | |
| ca++; | |
| rv = apr_sockaddr_info_get(&conf->backend_addrs, conf->host, | |
| APR_UNSPEC, conf->port, 0, cmd->pool); | |
| if (rv != APR_SUCCESS) { | |
| ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, rv, NULL, | |
| APLOGNO(02530) "Address %s could not be resolved", | |
| conf->backend); | |
| return apr_pstrcat(cmd->pool, | |
| dname, | |
| ": Error resolving backend address", | |
| NULL); | |
| } | |
| if (ca != argc) { | |
| return apr_pstrcat(cmd->pool, | |
| dname, | |
| ": Unexpected parameter ", | |
| argv[ca], | |
| NULL); | |
| } | |
| if (conf->is_authn) { | |
| apr_hash_set(fcgi_authn_providers, conf->name, APR_HASH_KEY_STRING, | |
| conf); | |
| ap_register_auth_provider(cmd->pool, AUTHN_PROVIDER_GROUP, | |
| conf->name, | |
| AUTHN_PROVIDER_VERSION, | |
| &fcgi_authn_provider, | |
| AP_AUTH_INTERNAL_PER_CONF); | |
| } | |
| if (conf->is_authz) { | |
| apr_hash_set(fcgi_authz_providers, conf->name, APR_HASH_KEY_STRING, | |
| conf); | |
| ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP, | |
| conf->name, | |
| AUTHZ_PROVIDER_VERSION, | |
| &fcgi_authz_provider, | |
| AP_AUTH_INTERNAL_PER_CONF); | |
| } | |
| return NULL; | |
| } | |
| static const command_rec fcgi_cmds[] = { | |
| AP_INIT_TAKE_ARGV("AuthnzFcgiDefineProvider", | |
| fcgi_define_provider, | |
| NULL, | |
| RSRC_CONF, | |
| "Define a FastCGI authn and/or authz provider"), | |
| AP_INIT_TAKE_ARGV("AuthnzFcgiCheckAuthnProvider", | |
| fcgi_check_authn_provider, | |
| NULL, | |
| OR_FILEINFO, | |
| "Enable/disable a FastCGI authorizer to handle " | |
| "check_authn phase"), | |
| {NULL} | |
| }; | |
| static int fcgi_pre_config(apr_pool_t *pconf, apr_pool_t *plog, | |
| apr_pool_t *ptemp) | |
| { | |
| fcgi_authn_providers = apr_hash_make(pconf); | |
| fcgi_authz_providers = apr_hash_make(pconf); | |
| return OK; | |
| } | |
| static void fcgi_register_hooks(apr_pool_t *p) | |
| { | |
| static const char * const auth_basic_runs_after_me[] = | |
| {"mod_auth_basic.c", NULL}; /* to allow for custom response */ | |
| ap_hook_pre_config(fcgi_pre_config, NULL, NULL, APR_HOOK_MIDDLE); | |
| ap_hook_check_authn(fcgi_check_authn, NULL, auth_basic_runs_after_me, | |
| APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF); | |
| } | |
| static void *create_dir_conf(apr_pool_t *p, char *dummy) | |
| { | |
| fcgi_dir_conf *dconf = apr_pcalloc(p, sizeof(fcgi_dir_conf)); | |
| dconf->authoritative = 1; | |
| return dconf; | |
| } | |
| static void *merge_dir_conf(apr_pool_t *p, void *basev, void *overridesv) | |
| { | |
| fcgi_dir_conf *a = (fcgi_dir_conf *)apr_pcalloc(p, sizeof(*a)); | |
| fcgi_dir_conf *base = (fcgi_dir_conf *)basev, | |
| *over = (fcgi_dir_conf *)overridesv; | |
| /* currently we just have a single directive applicable to a | |
| * directory, so if it is set then grab all fields from fcgi_dir_conf | |
| */ | |
| if (over->name) { | |
| memcpy(a, over, sizeof(*a)); | |
| } | |
| else { | |
| memcpy(a, base, sizeof(*a)); | |
| } | |
| return a; | |
| } | |
| AP_DECLARE_MODULE(authnz_fcgi) = | |
| { | |
| STANDARD20_MODULE_STUFF, | |
| create_dir_conf, /* dir config creater */ | |
| merge_dir_conf, /* dir merger */ | |
| NULL, /* server config */ | |
| NULL, /* merge server config */ | |
| fcgi_cmds, /* command apr_table_t */ | |
| fcgi_register_hooks /* register hooks */ | |
| }; |