mod_authnz_fcgi.c revision 504c4aa29f414902675559aa95d99b8707e03383
/* 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
*
*
* 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"
typedef struct {
const char *name; /* provider name */
const char *backend; /* backend address, as configured */
const char *host;
int is_authn;
int is_authz;
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
*/
char authoritative; /* fail request if user is rejected? */
char require_basic_auth; /* fail if client didn't send credentials? */
typedef struct {
/* If an "authnz" provider successfully authenticates, record
* the provider name here for checking during authz.
*/
const char *successful_authnz_provider;
#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
*/
request_rec *r,
const char *backend_name,
{
*/
int connected = 0;
SOCK_STREAM, 0, r->pool);
if (rv != APR_SUCCESS) {
"for target %s",
continue;
}
if (rv != APR_SUCCESS) {
continue;
}
connected = 1;
}
return rv;
}
{
"first address %pI, %c%c",
}
{
ap_add_cgi_vars(r);
if (apache_role) {
}
if (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
*/
/* Connection hop-by-hop header to prevent the CGI from hanging */
}
request_rec *r,
apr_socket_t *s,
char *buf,
{
if (rv != APR_SUCCESS) {
return rv;
}
#endif
return APR_SUCCESS;
}
request_rec *r,
apr_socket_t *s,
char *buf,
{
apr_size_t cumulative_len = 0;
do {
if (rv != APR_SUCCESS) {
return rv;
}
} while (cumulative_len < buflen);
return APR_SUCCESS;
}
request_rec *r,
apr_socket_t *s,
int nvec,
{
int i, offset;
for (i = 0; i < nvec; i++) {
#endif
}
offset = 0;
while (to_write) {
apr_size_t n = 0;
if (rv != APR_SUCCESS) {
break;
}
if (n > 0) {
written += n;
break; /* short circuit out */
offset++;
} else {
break;
}
}
}
}
return rv;
}
const fcgi_provider_conf *conf,
apr_socket_t *s, int role,
{
unsigned char farray[AP_FCGI_HEADER_LEN];
unsigned char abrb[AP_FCGI_HEADER_LEN];
sizeof(abrb), 0);
}
const fcgi_provider_conf *conf,
{
const char *fn = "send_environment";
const apr_array_header_t *envarr;
const apr_table_entry_t *elts;
unsigned char farray[AP_FCGI_HEADER_LEN];
char *body;
int i, next_elem, starting_elem;
if (APLOG_R_IS_LEVEL(r, APLOG_TRACE2)) {
continue;
}
"%s: '%s': '%s'",
}
}
/* Send envvars over in as many FastCGI records as it takes, */
next_elem = 0; /* starting with the first one */
* to AP_FCGI_MAX_CONTENT_LEN
*/
&next_elem);
if (!required_len) {
APR_SIZE_T_FMT " bytes",
/* skip this envvar and continue */
++next_elem;
continue;
}
/* only an unused element at the end of the array */
break;
}
APR_SIZE_T_FMT ", %d/%d elems processed so far",
/* we pre-compute, so we can't run out of space */
/* compute and encode must be in sync */
(apr_uint16_t)required_len, 0);
if (rv) {
return rv;
}
}
/* Envvars sent, so say we're done */
}
/*
* This header-state logic is from mod_proxy_fcgi.
*/
enum {
};
/* 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,
char *readbuf)
{
while (*itr) {
if (*itr == '\r') {
switch (*state) {
case HDR_STATE_GOT_CRLF:
break;
default:
break;
}
}
else if (*itr == '\n') {
switch (*state) {
case HDR_STATE_GOT_LF:
break;
case HDR_STATE_GOT_CR:
break;
case HDR_STATE_GOT_CRLFCR:
break;
default:
break;
}
}
else {
}
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()
*/
request_rec *r, apr_socket_t *s,
char *rspbuf,
{
apr_bucket *b;
apr_size_t orspbuflen = 0;
const char *fn = "handle_response";
int seen_end_of_headers = 0, done = 0;
if (rspbuflen) {
orspbuflen = *rspbuflen;
*rspbuflen = 0; /* unless we actually read something */
}
* we get AP_FCGI_END_REQUEST (done)
* or an error occurs.
*/
unsigned char farray[AP_FCGI_HEADER_LEN];
unsigned char plen;
unsigned char type;
unsigned char version;
if (rv != APR_SUCCESS) {
"entire header", fn);
break;
}
farray);
if (version != AP_FCGI_VERSION_1) {
rv = APR_EINVAL;
break;
}
if (rid != request_id) {
"request id %d, expected %d",
rv = APR_EINVAL;
break;
}
recv_again: /* if we need to keep reading more of a record's content */
} else {
readbuflen = clen;
}
/*
* Now get the actual content of the record.
*/
if (readbuflen != 0) {
if (rv != APR_SUCCESS) {
break;
}
}
switch (type) {
case AP_FCGI_STDOUT: /* Response headers and optional body */
if (clen != 0) {
r->connection->bucket_alloc);
if (!seen_end_of_headers) {
if (st == 1) {
int status;
seen_end_of_headers = 1;
status =
NULL,
"parsing -> %d/%d",
if (rspbuf) { /* caller wants to see response body,
* if any
*/
if (rspbuflen) {
*rspbuflen = orspbuflen;
}
if (tmprv != APR_SUCCESS) {
/* should not occur for these bucket types;
* does not indicate overflow
*/
"flattening response body",
fn);
}
}
"script headers from %s",
rv = APR_EINVAL;
break;
}
}
else {
/* We're still looking for the end of the
* headers, so this part of the data will need
* to persist. */
}
}
/* 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) {
}
if (clen > readbuflen) {
clen -= readbuflen;
goto recv_again; /* continue reading this record */
}
break;
case AP_FCGI_END_REQUEST:
done = 1;
break;
default:
break;
}
/*
*/
if (plen) {
if (rv != APR_SUCCESS) {
"padding",
fn);
break;
}
}
}
rv = APR_EINVAL;
fn);
}
return rv;
}
/* almost from mod_fcgid */
static int mod_fcgid_modify_auth_header(void *vars,
{
/* 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 */
return 1;
}
{
request_rec *r = vr;
return 1;
}
const char *password, const char *apache_role,
{
const char *fn = "req_rsp";
apr_socket_t *s;
if (rspbuflen) {
orspbuflen = *rspbuflen;
*rspbuflen = 0; /* unless we actually read something */
}
if (rv == APR_SUCCESS) {
if (rv != APR_SUCCESS) {
}
if (rv == APR_SUCCESS) {
if (rv != APR_SUCCESS) {
}
}
/* 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;
}
if (rv != APR_SUCCESS) {
}
}
apr_socket_close(s);
}
if (rv != APR_SUCCESS) {
/* some sort of mechanical problem */
}
else {
}
/* 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).
*/
* any values that end up
* in r->(anything)
*/
10);
r->err_headers_out, NULL);
}
}
static int fcgi_check_authn(request_rec *r)
{
const char *fn = "fcgi_check_authn";
const fcgi_provider_conf *conf;
const char *prov;
const char *auth_type;
int res;
return DECLINED;
}
auth_type = ap_auth_type(r);
"require-basic %s, user expr? %s type %s",
"password", fn);
if (dconf->require_basic_auth) {
return res;
}
}
}
if (!conf) {
return HTTP_INTERNAL_SERVER_ERROR;
}
if (APLOGrdebug(r)) {
log_provider_info(conf, r);
}
const char *err;
&err);
}
}
else if (user) {
"after calling authorizer: user expression "
"yielded empty string (variable not set?)",
fn);
}
else {
/* unexpected error, not even an empty string was returned */
"after calling authorizer: %s",
}
}
*
* Remember that the request was successfully authorized by this
* provider.
*/
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.
*/
"authoritative authorizer", fn);
}
else if (rspbuflen > 0) {
/* apr_brigade_flatten() interface :( */
"response body", fn);
}
}
}
}
const char *password)
{
const char *fn = "fcgi_check_password";
const fcgi_provider_conf *conf;
if (!prov) {
return AUTH_GENERAL_ERROR;
}
if (!conf) {
return AUTH_GENERAL_ERROR;
}
if (APLOGrdebug(r)) {
log_provider_info(conf, r);
}
/* combined authn and authz: FCGI_APACHE_ROLE not set */
*
* Remember that the request was successfully authorized by this
* provider.
*/
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 = {
NULL /* get-realm-hash not supported */
};
const char *require_line,
const void *parsed_require_line)
{
const char *fn = "fcgi_authz_check";
const fcgi_provider_conf *conf;
if (!prov) {
return AUTHZ_GENERAL_ERROR;
}
if (!conf) {
return AUTHZ_GENERAL_ERROR;
}
if (APLOGrdebug(r)) {
log_provider_info(conf, r);
}
if (!r->user) {
return AUTHZ_DENIED_NO_USER;
}
*
* If the provider already successfully authorized this request,
* success.
*/
if (rnotes
return AUTHZ_GRANTED;
}
else {
return AUTHZ_DENIED;
}
}
else {
return AUTHZ_GRANTED;
}
else if (r->status == HTTP_INTERNAL_SERVER_ERROR) {
return AUTHZ_GENERAL_ERROR;
}
else {
return AUTHZ_DENIED;
}
}
}
const void **parsed_require_line)
{
/* Allowed form: Require [not] registered-provider-name<EOS>
*/
return "mod_authnz_fcgi doesn't support restrictions on providers "
"(i.e., multiple require args)";
}
return NULL;
}
static const authz_provider fcgi_authz_provider = {
};
void *d,
int argc,
char *const argv[])
{
const char *dname = "AuthnzFcgiCheckAuthnProvider";
fcgi_dir_conf *dc = d;
int ca = 0;
}
ca++;
return "Options aren't supported with \"None\"";
}
}
int badarg = 0;
ca++;
/* at present, everything needs an argument */
"needs an argument", NULL);
}
ca++;
}
dc->authoritative = 0;
}
else {
badarg = 1;
}
}
}
}
dc->require_basic_auth = 0;
}
else {
badarg = 1;
}
}
const char *err;
if (err) {
}
}
else {
}
if (badarg) {
}
}
return NULL;
}
/* AuthnzFcgiAuthDefineProvider {authn|authz|authnz} provider-name \
* fcgi://backendhost:backendport/
*/
void *d,
int argc,
char *const argv[])
{
const char *dname = "AuthnzFcgiDefineProvider";
char *host;
if (!fcgi_backend_regex) {
"%s: failed to compile regexec '%s'",
}
if (err)
return err;
}
ca++;
}
}
}
else {
": Invalid provider type ",
NULL);
}
}
ca++;
NULL);
}
dname, ": backend-address '",
"' has invalid form",
NULL);
}
host += 1;
}
dname, ": backend-address '",
"' has invalid port",
NULL);
}
ca++;
if (rv != APR_SUCCESS) {
": Error resolving backend address",
NULL);
}
": Unexpected parameter ",
NULL);
}
conf);
}
conf);
}
return NULL;
}
static const command_rec fcgi_cmds[] = {
AP_INIT_TAKE_ARGV("AuthnzFcgiDefineProvider",
NULL,
AP_INIT_TAKE_ARGV("AuthnzFcgiCheckAuthnProvider",
NULL,
"check_authn phase"),
{NULL}
};
{
return OK;
}
static void fcgi_register_hooks(apr_pool_t *p)
{
static const char * const auth_basic_runs_after_me[] =
}
{
return dconf;
}
{
/* currently we just have a single directive applicable to a
* directory, so if it is set then grab all fields from fcgi_dir_conf
*/
}
else {
}
return a;
}
{
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 */
};