1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include "apr_strings.h"
21 #include "ap_provider.h"
23 #include "http_config.h"
24 #include "http_core.h"
25 #include "http_protocol.h"
26 #include "http_request.h"
28 #include "util_script.h"
29 #include "ap_provider.h"
31 #include "util_fcgi.h"
34 module AP_MODULE_DECLARE_DATA authnz_fcgi_module;
37 const char *name; /* provider name */
38 const char *backend; /* backend address, as configured */
41 apr_sockaddr_t *backend_addrs;
47 const char *name; /* provider name */
48 const char *default_user; /* this is user if authorizer returns
49 * success and a user expression yields
52 ap_expr_info_t *user_expr; /* expr to evaluate to set r->user */
53 char authoritative; /* fail request if user is rejected? */
54 char require_basic_auth; /* fail if client didn't send credentials? */
58 /* If an "authnz" provider successfully authenticates, record
59 * the provider name here for checking during authz.
61 const char *successful_authnz_provider;
64 static apr_hash_t *fcgi_authn_providers, *fcgi_authz_providers;
66 #define FCGI_IO_TIMEOUT apr_time_from_sec(30)
68 #ifndef NON200_RESPONSE_BUF_LEN
69 #define NON200_RESPONSE_BUF_LEN 8192
72 /* fcgi://{hostname|IPv4|IPv6}:port[/] */
73 #define FCGI_BACKEND_REGEX_STR "m%^fcgi://(.*):(\\d{1,5})/?$%"
76 * utility function to connect to a peer; generally useful, but
77 * wait for AF_UNIX support in this mod before thinking about how
78 * to make it available to other modules
80 static apr_status_t connect_to_peer(apr_socket_t **newsock,
82 apr_sockaddr_t *backend_addrs,
83 const char *backend_name,
84 apr_interval_time_t timeout)
86 apr_status_t rv = APR_EINVAL; /* returned if no backend addr was provided
89 apr_sockaddr_t *addr = backend_addrs;
91 while (addr && !connected) {
92 int loglevel = addr->next ? APLOG_DEBUG : APLOG_ERR;
93 rv = apr_socket_create(newsock, addr->family,
94 SOCK_STREAM, 0, r->pool);
95 if (rv != APR_SUCCESS) {
96 ap_log_rerror(APLOG_MARK, loglevel, rv, r,
97 APLOGNO(02494) "error creating family %d socket "
99 addr->family, backend_name);
104 apr_socket_opt_set(*newsock, APR_TCP_NODELAY, 1);
105 apr_socket_timeout_set(*newsock,
106 timeout ? timeout : r->server->timeout);
108 rv = apr_socket_connect(*newsock, addr);
109 if (rv != APR_SUCCESS) {
110 apr_socket_close(*newsock);
111 ap_log_rerror(APLOG_MARK, loglevel, rv, r,
112 APLOGNO(02495) "attempt to connect to %pI (%s) "
113 "failed", addr, backend_name);
125 static void log_provider_info(const fcgi_provider_conf *conf, request_rec *r)
127 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
128 APLOGNO(02496) "name %s, backend %s, host %s, port %d, "
129 "first address %pI, %c%c",
135 conf->is_authn ? 'N' : '_',
136 conf->is_authz ? 'Z' : '_');
139 static void setupenv(request_rec *r, const char *password, const char *apache_role)
141 ap_add_common_vars(r);
143 apr_table_setn(r->subprocess_env, "FCGI_ROLE", AP_FCGI_AUTHORIZER_STR);
145 apr_table_setn(r->subprocess_env, "FCGI_APACHE_ROLE", apache_role);
148 apr_table_setn(r->subprocess_env, "REMOTE_PASSWD", password);
150 /* Drop the variables CONTENT_LENGTH, PATH_INFO, PATH_TRANSLATED,
151 * SCRIPT_NAME and most Hop-By-Hop headers - EXCEPT we will pass
152 * PROXY_AUTH to allow CGI to perform proxy auth for httpd
154 apr_table_unset(r->subprocess_env, "CONTENT_LENGTH");
155 apr_table_unset(r->subprocess_env, "PATH_INFO");
156 apr_table_unset(r->subprocess_env, "PATH_TRANSLATED");
157 apr_table_unset(r->subprocess_env, "SCRIPT_NAME");
158 apr_table_unset(r->subprocess_env, "HTTP_KEEP_ALIVE");
159 apr_table_unset(r->subprocess_env, "HTTP_TE");
160 apr_table_unset(r->subprocess_env, "HTTP_TRAILER");
161 apr_table_unset(r->subprocess_env, "HTTP_TRANSFER_ENCODING");
162 apr_table_unset(r->subprocess_env, "HTTP_UPGRADE");
164 /* Connection hop-by-hop header to prevent the CGI from hanging */
165 apr_table_setn(r->subprocess_env, "HTTP_CONNECTION", "close");
168 static apr_status_t recv_data(const fcgi_provider_conf *conf,
176 rv = apr_socket_recv(s, buf, buflen);
177 if (rv != APR_SUCCESS) {
178 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
179 APLOGNO(02497) "Couldn't read from backend %s",
184 #if AP_MODULE_MAGIC_AT_LEAST(20130702,2)
185 ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data received",
186 buf, *buflen, AP_LOG_DATA_SHOW_OFFSET);
191 static apr_status_t recv_data_full(const fcgi_provider_conf *conf,
198 apr_size_t cumulative_len = 0;
202 readlen = buflen - cumulative_len;
203 rv = recv_data(conf, r, s, buf + cumulative_len, &readlen);
204 if (rv != APR_SUCCESS) {
207 cumulative_len += readlen;
208 } while (cumulative_len < buflen);
213 static apr_status_t sendv_data(const fcgi_provider_conf *conf,
220 apr_size_t to_write = 0, written = 0;
221 apr_status_t rv = APR_SUCCESS;
224 for (i = 0; i < nvec; i++) {
225 to_write += vec[i].iov_len;
226 #if AP_MODULE_MAGIC_AT_LEAST(20130702,2)
227 ap_log_rdata(APLOG_MARK, APLOG_TRACE5, r, "FastCGI data sent",
228 vec[i].iov_base, vec[i].iov_len, AP_LOG_DATA_SHOW_OFFSET);
235 rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n);
236 if (rv != APR_SUCCESS) {
237 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
238 APLOGNO(02498) "Sending data to %s failed",
244 if (written >= to_write)
245 break; /* short circuit out */
246 for (i = offset; i < nvec; ) {
247 if (n >= vec[i].iov_len) {
249 n -= vec[i++].iov_len;
252 vec[i].iov_base = (char *) vec[i].iov_base + n;
264 static apr_status_t send_begin_request(request_rec *r,
265 const fcgi_provider_conf *conf,
266 apr_socket_t *s, int role,
267 apr_uint16_t request_id)
270 ap_fcgi_header header;
271 unsigned char farray[AP_FCGI_HEADER_LEN];
272 ap_fcgi_begin_request_body brb;
273 unsigned char abrb[AP_FCGI_HEADER_LEN];
276 ap_fcgi_fill_in_header(&header, AP_FCGI_BEGIN_REQUEST, request_id,
278 ap_fcgi_fill_in_request_body(&brb, role, 0 /* *NOT* AP_FCGI_KEEP_CONN */);
280 ap_fcgi_header_to_array(&header, farray);
281 ap_fcgi_begin_request_body_to_array(&brb, abrb);
283 vec[0].iov_base = (void *)farray;
284 vec[0].iov_len = sizeof(farray);
285 vec[1].iov_base = (void *)abrb;
286 vec[1].iov_len = sizeof(abrb);
288 return sendv_data(conf, r, s, vec, 2, &len);
291 static apr_status_t send_environment(apr_socket_t *s,
292 const fcgi_provider_conf *conf,
293 request_rec *r, apr_uint16_t request_id,
294 apr_pool_t *temp_pool)
296 const char *fn = "send_environment";
297 const apr_array_header_t *envarr;
298 const apr_table_entry_t *elts;
300 ap_fcgi_header header;
301 unsigned char farray[AP_FCGI_HEADER_LEN];
304 apr_size_t avail_len, len, required_len;
305 int i, next_elem, starting_elem;
307 envarr = apr_table_elts(r->subprocess_env);
308 elts = (const apr_table_entry_t *) envarr->elts;
310 if (APLOG_R_IS_LEVEL(r, APLOG_TRACE2)) {
312 for (i = 0; i < envarr->nelts; ++i) {
316 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
319 !strcmp(elts[i].key, "REMOTE_PASSWD") ?
320 "XXXXXXXX" : elts[i].val);
324 /* Send envvars over in as many FastCGI records as it takes, */
325 next_elem = 0; /* starting with the first one */
327 avail_len = 16 * 1024; /* our limit per record, which could have been up
328 * to AP_FCGI_MAX_CONTENT_LEN
331 while (next_elem < envarr->nelts) {
332 starting_elem = next_elem;
333 required_len = ap_fcgi_encoded_env_len(r->subprocess_env,
338 if (next_elem < envarr->nelts) {
339 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
340 APLOGNO(02499) "couldn't encode envvar '%s' in %"
341 APR_SIZE_T_FMT " bytes",
342 elts[next_elem].key, avail_len);
343 /* skip this envvar and continue */
347 /* only an unused element at the end of the array */
351 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
352 APLOGNO(02500) "required len for encoding envvars: %"
353 APR_SIZE_T_FMT ", %d/%d elems processed so far",
354 required_len, next_elem, envarr->nelts);
356 body = apr_palloc(temp_pool, required_len);
357 rv = ap_fcgi_encode_env(r, r->subprocess_env, body, required_len,
359 /* we pre-compute, so we can't run out of space */
360 ap_assert(rv == APR_SUCCESS);
361 /* compute and encode must be in sync */
362 ap_assert(starting_elem == next_elem);
364 ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id,
365 (apr_uint16_t)required_len, 0);
366 ap_fcgi_header_to_array(&header, farray);
368 vec[0].iov_base = (void *)farray;
369 vec[0].iov_len = sizeof(farray);
370 vec[1].iov_base = body;
371 vec[1].iov_len = required_len;
373 rv = sendv_data(conf, r, s, vec, 2, &len);
374 apr_pool_clear(temp_pool);
381 /* Envvars sent, so say we're done */
382 ap_fcgi_fill_in_header(&header, AP_FCGI_PARAMS, request_id, 0, 0);
383 ap_fcgi_header_to_array(&header, farray);
385 vec[0].iov_base = (void *)farray;
386 vec[0].iov_len = sizeof(farray);
388 return sendv_data(conf, r, s, vec, 1, &len);
392 * This header-state logic is from mod_proxy_fcgi.
395 HDR_STATE_READING_HEADERS,
398 HDR_STATE_GOT_CRLFCR,
400 HDR_STATE_DONE_WITH_HEADERS
403 /* Try to find the end of the script headers in the response from the back
404 * end fastcgi server. STATE holds the current header parsing state for this
407 * Returns 0 if it can't find the end of the headers, and 1 if it found the
408 * end of the headers. */
409 static int handle_headers(request_rec *r, int *state,
410 const char *readbuf, apr_size_t readlen)
412 const char *itr = readbuf;
417 case HDR_STATE_GOT_CRLF:
418 *state = HDR_STATE_GOT_CRLFCR;
422 *state = HDR_STATE_GOT_CR;
426 else if (*itr == '\n') {
428 case HDR_STATE_GOT_LF:
429 *state = HDR_STATE_DONE_WITH_HEADERS;
432 case HDR_STATE_GOT_CR:
433 *state = HDR_STATE_GOT_CRLF;
436 case HDR_STATE_GOT_CRLFCR:
437 *state = HDR_STATE_DONE_WITH_HEADERS;
441 *state = HDR_STATE_GOT_LF;
446 *state = HDR_STATE_READING_HEADERS;
449 if (*state == HDR_STATE_DONE_WITH_HEADERS)
455 if (*state == HDR_STATE_DONE_WITH_HEADERS) {
463 * handle_response() is based on mod_proxy_fcgi's dispatch()
465 static apr_status_t handle_response(const fcgi_provider_conf *conf,
466 request_rec *r, apr_socket_t *s,
467 apr_pool_t *temp_pool,
468 apr_uint16_t request_id,
470 apr_size_t *rspbuflen)
473 apr_bucket_brigade *ob;
474 apr_size_t orspbuflen = 0;
475 apr_status_t rv = APR_SUCCESS;
476 const char *fn = "handle_response";
477 int header_state = HDR_STATE_READING_HEADERS;
478 int seen_end_of_headers = 0, done = 0;
481 orspbuflen = *rspbuflen;
482 *rspbuflen = 0; /* unless we actually read something */
485 ob = apr_brigade_create(r->pool, r->connection->bucket_alloc);
487 while (!done && rv == APR_SUCCESS) { /* Keep reading FastCGI records until
488 * we get AP_FCGI_END_REQUEST (done)
489 * or an error occurs.
491 apr_size_t readbuflen;
494 char readbuf[AP_IOBUFSIZE];
495 unsigned char farray[AP_FCGI_HEADER_LEN];
498 unsigned char version;
500 rv = recv_data_full(conf, r, s, (char *)farray, AP_FCGI_HEADER_LEN);
501 if (rv != APR_SUCCESS) {
502 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
503 APLOGNO(02501) "%s: Error occurred before reading "
504 "entire header", fn);
508 ap_fcgi_header_fields_from_array(&version, &type, &rid, &clen, &plen,
511 if (version != AP_FCGI_VERSION_1) {
512 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
513 APLOGNO(02502) "%s: Got bogus FastCGI header "
514 "version %d", fn, (int)version);
519 if (rid != request_id) {
520 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
521 APLOGNO(02503) "%s: Got bogus FastCGI header "
522 "request id %d, expected %d",
523 fn, rid, request_id);
528 recv_again: /* if we need to keep reading more of a record's content */
530 if (clen > sizeof(readbuf)) {
531 readbuflen = sizeof(readbuf);
537 * Now get the actual content of the record.
539 if (readbuflen != 0) {
540 rv = recv_data(conf, r, s, readbuf, &readbuflen);
541 if (rv != APR_SUCCESS) {
547 case AP_FCGI_STDOUT: /* Response headers and optional body */
549 b = apr_bucket_transient_create(readbuf,
551 r->connection->bucket_alloc);
553 APR_BRIGADE_INSERT_TAIL(ob, b);
555 if (!seen_end_of_headers) {
556 int st = handle_headers(r, &header_state,
557 readbuf, readbuflen);
562 seen_end_of_headers = 1;
565 ap_scan_script_header_err_brigade_ex(r, ob,
568 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
569 APLOGNO(02504) "%s: script header "
571 fn, status, r->status);
573 if (rspbuf) { /* caller wants to see response body,
579 *rspbuflen = orspbuflen;
581 tmprv = apr_brigade_flatten(ob, rspbuf, rspbuflen);
582 if (tmprv != APR_SUCCESS) {
583 /* should not occur for these bucket types;
584 * does not indicate overflow
586 ap_log_rerror(APLOG_MARK, APLOG_ERR, tmprv, r,
587 APLOGNO(02505) "%s: error "
588 "flattening response body",
595 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
596 APLOGNO(02506) "%s: Error parsing "
597 "script headers from %s",
602 apr_pool_clear(temp_pool);
605 /* We're still looking for the end of the
606 * headers, so this part of the data will need
608 apr_bucket_setaside(b, temp_pool);
612 /* If we didn't read all the data go back and get the
614 if (clen > readbuflen) {
621 case AP_FCGI_STDERR: /* Text to log */
623 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
624 APLOGNO(02507) "%s: Logged from %s: '%s'",
625 fn, conf->backend, readbuf);
628 if (clen > readbuflen) {
630 goto recv_again; /* continue reading this record */
634 case AP_FCGI_END_REQUEST:
639 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
640 APLOGNO(02508) "%s: Got bogus FastCGI record type "
644 /* Leave on above switch's inner error. */
645 if (rv != APR_SUCCESS) {
650 * Read/discard any trailing padding.
653 rv = recv_data_full(conf, r, s, readbuf, plen);
654 if (rv != APR_SUCCESS) {
655 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
656 APLOGNO(02509) "%s: Error occurred reading "
664 apr_brigade_cleanup(ob);
666 if (rv == APR_SUCCESS && !seen_end_of_headers) {
668 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
669 APLOGNO(02510) "%s: Never reached end of script headers",
676 /* almost from mod_fcgid */
677 static int mod_fcgid_modify_auth_header(void *vars,
678 const char *key, const char *val)
680 /* When the application gives a 200 response, the server ignores response
681 headers whose names aren't prefixed with Variable- prefix, and ignores
682 any response content */
683 if (ap_casecmpstrn(key, "Variable-", 9) == 0)
684 apr_table_setn(vars, key, val);
688 static int fix_auth_header(void *vr, const char *key, const char *val)
692 ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "moving %s->%s", key, val);
693 apr_table_unset(r->err_headers_out, key);
694 apr_table_setn(r->subprocess_env, key + 9, val);
698 static void req_rsp(request_rec *r, const fcgi_provider_conf *conf,
699 const char *password, const char *apache_role,
700 char *rspbuf, apr_size_t *rspbuflen)
702 const char *fn = "req_rsp";
703 apr_pool_t *temp_pool;
704 apr_size_t orspbuflen = 0;
707 apr_table_t *saved_subprocess_env =
708 apr_table_copy(r->pool, r->subprocess_env);
711 orspbuflen = *rspbuflen;
712 *rspbuflen = 0; /* unless we actually read something */
715 apr_pool_create(&temp_pool, r->pool);
717 setupenv(r, password, apache_role);
719 rv = connect_to_peer(&s, r, conf->backend_addrs,
720 conf->backend, FCGI_IO_TIMEOUT);
721 if (rv == APR_SUCCESS) {
722 apr_uint16_t request_id = 1;
724 rv = send_begin_request(r, conf, s, AP_FCGI_AUTHORIZER, request_id);
725 if (rv != APR_SUCCESS) {
726 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
727 APLOGNO(02511) "%s: Failed writing request to %s",
731 if (rv == APR_SUCCESS) {
732 rv = send_environment(s, conf, r, request_id, temp_pool);
733 if (rv != APR_SUCCESS) {
734 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
735 APLOGNO(02512) "%s: Failed writing environment "
736 "to %s", fn, conf->backend);
740 /* The responder owns the request body, not the authorizer.
741 * Don't even send an empty AP_FCGI_STDIN block. libfcgi doesn't care,
742 * but it wasn't sent to authorizers by mod_fastcgi or mod_fcgi and
743 * may be unhandled by the app. Additionally, the FastCGI spec does
744 * not mention FCGI_STDIN in the Authorizer description, though it
745 * does describe FCGI_STDIN elsewhere in more general terms than
746 * simply a wrapper for the client's request body.
749 if (rv == APR_SUCCESS) {
751 *rspbuflen = orspbuflen;
753 rv = handle_response(conf, r, s, temp_pool, request_id, rspbuf,
755 if (rv != APR_SUCCESS) {
756 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
757 APLOGNO(02514) "%s: Failed handling response "
758 "from %s", fn, conf->backend);
765 if (rv != APR_SUCCESS) {
766 /* some sort of mechanical problem */
767 r->status = HTTP_INTERNAL_SERVER_ERROR;
770 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
771 APLOGNO(02515) "%s: Received HTTP status %d",
775 r->subprocess_env = saved_subprocess_env;
777 if (r->status == HTTP_OK) {
778 /* An Authorizer application's 200 response may include headers
779 * whose names are prefixed with Variable-, and they should be
780 * available to subsequent phases via subprocess_env (and yanked
781 * from the client response).
783 apr_table_t *vars = apr_table_make(temp_pool, /* not used to allocate
784 * any values that end up
788 apr_table_do(mod_fcgid_modify_auth_header, vars,
789 r->err_headers_out, NULL);
790 apr_table_do(fix_auth_header, r, vars, NULL);
793 apr_pool_destroy(temp_pool);
796 static int fcgi_check_authn(request_rec *r)
798 const char *fn = "fcgi_check_authn";
799 fcgi_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
800 &authnz_fcgi_module);
801 const char *password = NULL;
802 const fcgi_provider_conf *conf;
804 const char *auth_type;
805 char rspbuf[NON200_RESPONSE_BUF_LEN + 1]; /* extra byte for '\0' */
806 apr_size_t rspbuflen = sizeof rspbuf - 1;
809 prov = dconf && dconf->name ? dconf->name : NULL;
811 if (!prov || !ap_casecmpstr(prov, "None")) {
815 auth_type = ap_auth_type(r);
817 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
818 APLOGNO(02516) "%s, prov %s, authoritative %s, "
819 "require-basic %s, user expr? %s type %s",
821 dconf->authoritative ? "yes" : "no",
822 dconf->require_basic_auth ? "yes" : "no",
823 dconf->user_expr ? "yes" : "no",
826 if (auth_type && !ap_casecmpstr(auth_type, "Basic")) {
827 if ((res = ap_get_basic_auth_pw(r, &password))) {
828 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
829 APLOGNO(02517) "%s: couldn't retrieve basic auth "
831 if (dconf->require_basic_auth) {
838 conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING);
840 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
841 APLOGNO(02518) "%s: can't find config for provider %s",
843 return HTTP_INTERNAL_SERVER_ERROR;
846 if (APLOGrdebug(r)) {
847 log_provider_info(conf, r);
850 req_rsp(r, conf, password, AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR,
853 if (r->status == HTTP_OK) {
854 if (dconf->user_expr) {
856 const char *user = ap_expr_str_exec(r, dconf->user_expr,
858 if (user && strlen(user)) {
859 r->user = apr_pstrdup(r->pool, user);
860 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
861 APLOGNO(02519) "%s: Setting user to '%s'",
864 else if (user && dconf->default_user) {
865 r->user = apr_pstrdup(r->pool, dconf->default_user);
868 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
869 APLOGNO(02520) "%s: Failure extracting user "
870 "after calling authorizer: user expression "
871 "yielded empty string (variable not set?)",
873 r->status = HTTP_INTERNAL_SERVER_ERROR;
876 /* unexpected error, not even an empty string was returned */
877 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
878 APLOGNO(02521) "%s: Failure extracting user "
879 "after calling authorizer: %s",
881 r->status = HTTP_INTERNAL_SERVER_ERROR;
884 if (conf->is_authz) {
885 /* combined authn/authz phase, so app won't be invoked for authz
887 * Remember that the request was successfully authorized by this
890 fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes));
891 rnotes->successful_authnz_provider = conf->name;
892 ap_set_module_config(r->request_config, &authnz_fcgi_module,
898 * For Authorizer response status values other than "200" (OK), the
899 * Web server denies access and sends the response status, headers,
900 * and content back to the HTTP client.
902 * This only makes sense if this authorizer is authoritative.
904 if (rspbuflen > 0 && !dconf->authoritative) {
905 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
906 APLOGNO(02522) "%s: Ignoring response body from non-"
907 "authoritative authorizer", fn);
909 else if (rspbuflen > 0) {
910 if (rspbuflen == sizeof rspbuf - 1) {
911 /* apr_brigade_flatten() interface :( */
912 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
913 APLOGNO(02523) "%s: possible overflow handling "
914 "response body", fn);
916 rspbuf[rspbuflen] = '\0'; /* we reserved an extra byte for '\0' */
917 ap_custom_response(r, r->status, rspbuf); /* API makes a copy */
921 return r->status == HTTP_OK ?
922 OK : dconf->authoritative ? r->status : DECLINED;
925 static authn_status fcgi_check_password(request_rec *r, const char *user,
926 const char *password)
928 const char *fn = "fcgi_check_password";
929 const char *prov = apr_table_get(r->notes, AUTHN_PROVIDER_NAME_NOTE);
930 const fcgi_provider_conf *conf;
932 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
933 APLOGNO(02524) "%s(%s, XXX): provider %s",
937 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
938 APLOGNO(02525) "%s: provider note isn't set", fn);
939 return AUTH_GENERAL_ERROR;
942 conf = apr_hash_get(fcgi_authn_providers, prov, APR_HASH_KEY_STRING);
944 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
945 APLOGNO(02526) "%s: can't find config for provider %s",
947 return AUTH_GENERAL_ERROR;
950 if (APLOGrdebug(r)) {
951 log_provider_info(conf, r);
954 req_rsp(r, conf, password,
955 /* combined authn and authz: FCGI_APACHE_ROLE not set */
956 conf->is_authz ? NULL : AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR,
959 if (r->status == HTTP_OK) {
960 if (conf->is_authz) {
961 /* combined authn/authz phase, so app won't be invoked for authz
963 * Remember that the request was successfully authorized by this
966 fcgi_request_notes *rnotes = apr_palloc(r->pool, sizeof(*rnotes));
967 rnotes->successful_authnz_provider = conf->name;
968 ap_set_module_config(r->request_config, &authnz_fcgi_module,
973 else if (r->status == HTTP_INTERNAL_SERVER_ERROR) {
974 return AUTH_GENERAL_ERROR;
981 static const authn_provider fcgi_authn_provider = {
982 &fcgi_check_password,
983 NULL /* get-realm-hash not supported */
986 static authz_status fcgi_authz_check(request_rec *r,
987 const char *require_line,
988 const void *parsed_require_line)
990 const char *fn = "fcgi_authz_check";
991 const char *prov = apr_table_get(r->notes, AUTHZ_PROVIDER_NAME_NOTE);
992 const fcgi_provider_conf *conf;
994 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
995 APLOGNO(02527) "%s(%s)", fn, require_line);
998 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
999 APLOGNO(02528) "%s: provider note isn't set", fn);
1000 return AUTHZ_GENERAL_ERROR;
1003 conf = apr_hash_get(fcgi_authz_providers, prov, APR_HASH_KEY_STRING);
1005 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r,
1006 APLOGNO(02529) "%s: can't find config for provider %s",
1008 return AUTHZ_GENERAL_ERROR;
1011 if (APLOGrdebug(r)) {
1012 log_provider_info(conf, r);
1016 return AUTHZ_DENIED_NO_USER;
1019 if (conf->is_authn) {
1020 /* combined authn/authz phase, so app won't be invoked for authz
1022 * If the provider already successfully authorized this request,
1025 fcgi_request_notes *rnotes = ap_get_module_config(r->request_config,
1026 &authnz_fcgi_module);
1028 && rnotes->successful_authnz_provider
1029 && !strcmp(rnotes->successful_authnz_provider, conf->name)) {
1030 return AUTHZ_GRANTED;
1033 return AUTHZ_DENIED;
1037 req_rsp(r, conf, NULL, AP_FCGI_APACHE_ROLE_AUTHORIZER_STR, NULL, NULL);
1039 if (r->status == HTTP_OK) {
1040 return AUTHZ_GRANTED;
1042 else if (r->status == HTTP_INTERNAL_SERVER_ERROR) {
1043 return AUTHZ_GENERAL_ERROR;
1046 return AUTHZ_DENIED;
1051 static const char *fcgi_authz_parse(cmd_parms *cmd, const char *require_line,
1052 const void **parsed_require_line)
1054 /* Allowed form: Require [not] registered-provider-name<EOS>
1056 if (strcmp(require_line, "")) {
1057 return "mod_authnz_fcgi doesn't support restrictions on providers "
1058 "(i.e., multiple require args)";
1064 static const authz_provider fcgi_authz_provider = {
1069 static const char *fcgi_check_authn_provider(cmd_parms *cmd,
1074 const char *dname = "AuthnzFcgiCheckAuthnProvider";
1075 fcgi_dir_conf *dc = d;
1079 return apr_pstrcat(cmd->pool, dname, ": No provider given", NULL);
1082 dc->name = argv[ca];
1085 if (!strcasecmp(dc->name, "None")) {
1087 return "Options aren't supported with \"None\"";
1092 const char *var = argv[ca], *val;
1097 /* at present, everything needs an argument */
1099 return apr_pstrcat(cmd->pool, dname, ": ", var,
1100 "needs an argument", NULL);
1106 if (!strcasecmp(var, "Authoritative")) {
1107 if (!strcasecmp(val, "On")) {
1108 dc->authoritative = 1;
1110 else if (!strcasecmp(val, "Off")) {
1111 dc->authoritative = 0;
1117 else if (!strcasecmp(var, "DefaultUser")) {
1118 dc->default_user = val;
1120 else if (!strcasecmp(var, "RequireBasicAuth")) {
1121 if (!strcasecmp(val, "On")) {
1122 dc->require_basic_auth = 1;
1124 else if (!strcasecmp(val, "Off")) {
1125 dc->require_basic_auth = 0;
1131 else if (!strcasecmp(var, "UserExpr")) {
1133 int flags = AP_EXPR_FLAG_DONT_VARY | AP_EXPR_FLAG_RESTRICTED
1134 | AP_EXPR_FLAG_STRING_RESULT;
1136 dc->user_expr = ap_expr_parse_cmd(cmd, val,
1139 return apr_psprintf(cmd->pool, "%s: Error parsing '%s': '%s'",
1144 return apr_pstrcat(cmd->pool, dname, ": Unexpected option '",
1148 return apr_pstrcat(cmd->pool, dname, ": Bad argument '",
1149 val, "' to option '", var, "'", NULL);
1156 /* AuthnzFcgiAuthDefineProvider {authn|authz|authnz} provider-name \
1157 * fcgi://backendhost:backendport/
1159 static const char *fcgi_define_provider(cmd_parms *cmd,
1164 const char *dname = "AuthnzFcgiDefineProvider";
1165 ap_rxplus_t *fcgi_backend_regex;
1168 const char *err, *stype;
1169 fcgi_provider_conf *conf = apr_pcalloc(cmd->pool, sizeof(*conf));
1170 int ca = 0, rc, port;
1172 fcgi_backend_regex = ap_rxplus_compile(cmd->pool, FCGI_BACKEND_REGEX_STR);
1173 if (!fcgi_backend_regex) {
1174 return apr_psprintf(cmd->pool,
1175 "%s: failed to compile regexec '%s'",
1176 dname, FCGI_BACKEND_REGEX_STR);
1179 err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
1184 return apr_pstrcat(cmd->pool, dname, ": No type given", NULL);
1190 if (!strcasecmp(stype, "authn")) {
1193 else if (!strcasecmp(stype, "authz")) {
1196 else if (!strcasecmp(stype, "authnz")) {
1197 conf->is_authn = conf->is_authz = 1;
1200 return apr_pstrcat(cmd->pool,
1202 ": Invalid provider type ",
1208 return apr_pstrcat(cmd->pool, dname, ": No provider name given", NULL);
1210 conf->name = argv[ca];
1214 return apr_pstrcat(cmd->pool, dname, ": No backend-address given",
1218 rc = ap_rxplus_exec(cmd->pool, fcgi_backend_regex, argv[ca], NULL);
1219 if (!rc || ap_rxplus_nmatch(fcgi_backend_regex) != 3) {
1220 return apr_pstrcat(cmd->pool,
1221 dname, ": backend-address '",
1223 "' has invalid form",
1227 host = ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 1);
1228 if (host[0] == '[' && host[strlen(host) - 1] == ']') {
1230 host[strlen(host) - 1] = '\0';
1233 port = atoi(ap_rxplus_pmatch(cmd->pool, fcgi_backend_regex, 2));
1235 return apr_pstrcat(cmd->pool,
1236 dname, ": backend-address '",
1238 "' has invalid port",
1242 conf->backend = argv[ca];
1247 rv = apr_sockaddr_info_get(&conf->backend_addrs, conf->host,
1248 APR_UNSPEC, conf->port, 0, cmd->pool);
1249 if (rv != APR_SUCCESS) {
1250 ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_CRIT, rv, NULL,
1251 APLOGNO(02530) "Address %s could not be resolved",
1253 return apr_pstrcat(cmd->pool,
1255 ": Error resolving backend address",
1260 return apr_pstrcat(cmd->pool,
1262 ": Unexpected parameter ",
1267 if (conf->is_authn) {
1268 apr_hash_set(fcgi_authn_providers, conf->name, APR_HASH_KEY_STRING,
1270 ap_register_auth_provider(cmd->pool, AUTHN_PROVIDER_GROUP,
1272 AUTHN_PROVIDER_VERSION,
1273 &fcgi_authn_provider,
1274 AP_AUTH_INTERNAL_PER_CONF);
1277 if (conf->is_authz) {
1278 apr_hash_set(fcgi_authz_providers, conf->name, APR_HASH_KEY_STRING,
1280 ap_register_auth_provider(cmd->pool, AUTHZ_PROVIDER_GROUP,
1282 AUTHZ_PROVIDER_VERSION,
1283 &fcgi_authz_provider,
1284 AP_AUTH_INTERNAL_PER_CONF);
1290 static const command_rec fcgi_cmds[] = {
1291 AP_INIT_TAKE_ARGV("AuthnzFcgiDefineProvider",
1292 fcgi_define_provider,
1295 "Define a FastCGI authn and/or authz provider"),
1297 AP_INIT_TAKE_ARGV("AuthnzFcgiCheckAuthnProvider",
1298 fcgi_check_authn_provider,
1301 "Enable/disable a FastCGI authorizer to handle "
1302 "check_authn phase"),
1307 static int fcgi_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
1310 fcgi_authn_providers = apr_hash_make(pconf);
1311 fcgi_authz_providers = apr_hash_make(pconf);
1316 static void fcgi_register_hooks(apr_pool_t *p)
1318 static const char * const auth_basic_runs_after_me[] =
1319 {"mod_auth_basic.c", NULL}; /* to allow for custom response */
1321 ap_hook_pre_config(fcgi_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
1322 ap_hook_check_authn(fcgi_check_authn, NULL, auth_basic_runs_after_me,
1323 APR_HOOK_MIDDLE, AP_AUTH_INTERNAL_PER_CONF);
1326 static void *create_dir_conf(apr_pool_t *p, char *dummy)
1328 fcgi_dir_conf *dconf = apr_pcalloc(p, sizeof(fcgi_dir_conf));
1330 dconf->authoritative = 1;
1334 static void *merge_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
1336 fcgi_dir_conf *a = (fcgi_dir_conf *)apr_pcalloc(p, sizeof(*a));
1337 fcgi_dir_conf *base = (fcgi_dir_conf *)basev,
1338 *over = (fcgi_dir_conf *)overridesv;
1340 /* currently we just have a single directive applicable to a
1341 * directory, so if it is set then grab all fields from fcgi_dir_conf
1344 memcpy(a, over, sizeof(*a));
1347 memcpy(a, base, sizeof(*a));
1353 AP_DECLARE_MODULE(authnz_fcgi) =
1355 STANDARD20_MODULE_STUFF,
1356 create_dir_conf, /* dir config creater */
1357 merge_dir_conf, /* dir merger */
1358 NULL, /* server config */
1359 NULL, /* merge server config */
1360 fcgi_cmds, /* command apr_table_t */
1361 fcgi_register_hooks /* register hooks */