I'm planning to add FastCGI structures and a few support routines to core for (initially) sharing between mod_authnz_fcgi and mod_proxy_fcgi. The size is pretty small so <em>to me it seems that</em> the overhead of creating some hypothetical mod_fcgi_util and having uglier APIs (optional function load, handling error path for missing module, etc.) doesn't seem warranted. But YMMV, so please follow up if you disagree.
Attached is the patch to give you an idea of what I'm talking about. It is largely lifted from mod_proxy_fcgi, though it doesn't look much different from mod_fcgid or the spec or presumably any other implementation. Some of the code from mod_proxy_fcgi has been made more general (e.g., separating building of protocol data from I/O). Planned changes before commit: * Fix ap_fcgi_encoded_env_len()/ap_fcgi_encode_env() so that it is straightforward to process more of the environment table after sending some of the environment in a FastCGI record. (Currently mod_proxy_fcgi truncates once it reaches the limit for a single FastCGI record, but we should be able to send over a larger env using multiple records. What is in the patch was an incomplete first attempt.) * Windows and other platform build changes * See if symbol names need changing for brevity or readability -- Born in Roswell... married an alien... http://emptyhammock.com/
Index: server/Makefile.in =================================================================== --- server/Makefile.in (revision 1512807) +++ server/Makefile.in (working copy) @@ -7,7 +7,7 @@ LTLIBRARY_NAME = libmain.la LTLIBRARY_SOURCES = \ - config.c log.c main.c vhost.c util.c \ + config.c log.c main.c vhost.c util.c util_fcgi.c \ util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \ connection.c listen.c util_mutex.c mpm_common.c mpm_unix.c \ util_charset.c util_cookies.c util_debug.c util_xml.c \ Index: server/util_fcgi.c =================================================================== --- server/util_fcgi.c (revision 0) +++ server/util_fcgi.c (working copy) @@ -0,0 +1,252 @@ +/* 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 "httpd.h" +#include "http_core.h" +#include "http_log.h" +#include "util_fcgi.h" + +/* we know core's module_index is 0 */ +#undef APLOG_MODULE_INDEX +#define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX + +/* + * The below 3 functions serve to map the FCGI structs + * back and forth between an 8 byte array. We do this to avoid + * any potential padding issues when we send or read these + * structures. + * + * NOTE: These have specific internal knowledge of the + * layout of the fcgi_header and fcgi_begin_request_body + * structs! + */ +AP_DECLARE(void) ap_fcgi_header_to_array(ap_fcgi_header *h, + unsigned char a[]) +{ + a[AP_FCGI_HDR_VERSION_OFFSET] = h->version; + a[AP_FCGI_HDR_TYPE_OFFSET] = h->type; + a[AP_FCGI_HDR_REQUEST_ID_B1_OFFSET] = h->requestIdB1; + a[AP_FCGI_HDR_REQUEST_ID_B0_OFFSET] = h->requestIdB0; + a[AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET] = h->contentLengthB1; + a[AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET] = h->contentLengthB0; + a[AP_FCGI_HDR_PADDING_LEN_OFFSET] = h->paddingLength; + a[AP_FCGI_HDR_RESERVED_OFFSET] = h->reserved; +} + +AP_DECLARE(void) ap_fcgi_header_from_array(ap_fcgi_header *h, + unsigned char a[]) +{ + h->version = a[AP_FCGI_HDR_VERSION_OFFSET]; + h->type = a[AP_FCGI_HDR_TYPE_OFFSET]; + h->requestIdB1 = a[AP_FCGI_HDR_REQUEST_ID_B1_OFFSET]; + h->requestIdB0 = a[AP_FCGI_HDR_REQUEST_ID_B0_OFFSET]; + h->contentLengthB1 = a[AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET]; + h->contentLengthB0 = a[AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET]; + h->paddingLength = a[AP_FCGI_HDR_PADDING_LEN_OFFSET]; + h->reserved = a[AP_FCGI_HDR_RESERVED_OFFSET]; +} + +AP_DECLARE(void) ap_fcgi_begin_request_body_to_array(ap_fcgi_begin_request_body *h, + unsigned char a[]) +{ + a[AP_FCGI_BRB_ROLEB1_OFFSET] = h->roleB1; + a[AP_FCGI_BRB_ROLEB0_OFFSET] = h->roleB0; + a[AP_FCGI_BRB_FLAGS_OFFSET] = h->flags; + a[AP_FCGI_BRB_RESERVED0_OFFSET] = h->reserved[0]; + a[AP_FCGI_BRB_RESERVED1_OFFSET] = h->reserved[1]; + a[AP_FCGI_BRB_RESERVED2_OFFSET] = h->reserved[2]; + a[AP_FCGI_BRB_RESERVED3_OFFSET] = h->reserved[3]; + a[AP_FCGI_BRB_RESERVED4_OFFSET] = h->reserved[4]; +} + +AP_DECLARE(void) ap_fcgi_fill_in_header(ap_fcgi_header *header, + unsigned char type, + apr_uint16_t request_id, + apr_uint16_t content_len, + unsigned char padding_len) +{ + header->version = AP_FCGI_VERSION_1; + + header->type = type; + + header->requestIdB1 = ((request_id >> 8) & 0xff); + header->requestIdB0 = ((request_id) & 0xff); + + header->contentLengthB1 = ((content_len >> 8) & 0xff); + header->contentLengthB0 = ((content_len) & 0xff); + + header->paddingLength = padding_len; + + header->reserved = 0; +} + +AP_DECLARE(void) ap_fcgi_fill_in_request_body(ap_fcgi_begin_request_body *brb, + int role, + unsigned char flags) +{ + brb->roleB1 = ((role >> 8) & 0xff); + brb->roleB0 = (role & 0xff); + brb->flags = flags; + brb->reserved[0] = 0; + brb->reserved[1] = 0; + brb->reserved[2] = 0; + brb->reserved[3] = 0; + brb->reserved[4] = 0; +} + +AP_DECLARE(apr_size_t) ap_fcgi_encoded_env_len(request_rec *r, + apr_table_t *env, + apr_size_t maxlen) +{ + const apr_array_header_t *envarr; + const apr_table_entry_t *elts; + apr_size_t envlen, actualenvlen; + int i; + + envarr = apr_table_elts(r->subprocess_env); + elts = (const apr_table_entry_t *) envarr->elts; + + /* envlen - speculative, may overflow the limit + * actualenvlen - len required without overflowing + */ + envlen = actualenvlen = 0; + for (i = 0; i < envarr->nelts; ++i) { + apr_size_t keylen, vallen; + + if (! elts[i].key) { + continue; + } + + keylen = strlen(elts[i].key); + + if (keylen >> 7 == 0) { + envlen += 1; + } + else { + envlen += 4; + } + + envlen += keylen; + + vallen = strlen(elts[i].val); + + if (vallen >> 7 == 0) { + envlen += 1; + } + else { + envlen += 4; + } + + envlen += vallen; + + if (envlen > maxlen) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + APLOGNO() "ap_fcgi_encoded_env_len: truncating " + "environment to %" APR_SIZE_T_FMT " bytes and " + "%d elements", + actualenvlen, i); + break; + } + + actualenvlen = envlen; + } + + return actualenvlen; +} + +AP_DECLARE(apr_status_t) ap_fcgi_encode_env(request_rec *r, + apr_table_t *env, + void *buffer, + apr_size_t buflen) +{ + const apr_array_header_t *envarr; + const apr_table_entry_t *elts; + char *itr; + int i; + + envarr = apr_table_elts(r->subprocess_env); + elts = (const apr_table_entry_t *) envarr->elts; + + itr = buffer; + + for (i = 0; i < envarr->nelts; ++i) { + apr_size_t keylen, vallen; + + if (!elts[i].key) { + continue; + } + + keylen = strlen(elts[i].key); + + if (keylen >> 7 == 0) { + if (buflen < 1) { + return APR_EINVAL; /* overflow */ + } + itr[0] = keylen & 0xff; + itr += 1; + buflen -= 1; + } + else { + if (buflen < 4) { + return APR_EINVAL; /* overflow */ + } + itr[0] = ((keylen >> 24) & 0xff) | 0x80; + itr[1] = ((keylen >> 16) & 0xff); + itr[2] = ((keylen >> 8) & 0xff); + itr[3] = ((keylen) & 0xff); + itr += 4; + buflen -= 4; + } + + vallen = strlen(elts[i].val); + + if (vallen >> 7 == 0) { + if (buflen < 1) { + return APR_EINVAL; /* overflow */ + } + itr[0] = vallen & 0xff; + itr += 1; + buflen -= 1; + } + else { + if (buflen < 4) { + return APR_EINVAL; /* overflow */ + } + itr[0] = ((vallen >> 24) & 0xff) | 0x80; + itr[1] = ((vallen >> 16) & 0xff); + itr[2] = ((vallen >> 8) & 0xff); + itr[3] = ((vallen) & 0xff); + itr += 4; + buflen -= 1; + } + + if (buflen < keylen) { + return APR_EINVAL; /* overflow */ + } + memcpy(itr, elts[i].key, keylen); + itr += keylen; + buflen -= keylen; + + if (buflen < vallen) { + return APR_EINVAL; /* overflow */ + } + memcpy(itr, elts[i].val, vallen); + itr += vallen; + buflen -= vallen; + } + + return APR_SUCCESS; +} Index: include/util_fcgi.h =================================================================== --- include/util_fcgi.h (revision 0) +++ include/util_fcgi.h (working copy) @@ -0,0 +1,235 @@ +/* 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 util_fcgi.h + * @brief FastCGI protocol defitions and support routines + * + * @defgroup APACHE_CORE_FASTCGI FastCGI Tools + * @ingroup APACHE_CORE + * @{ + */ + +#ifndef APACHE_UTIL_FCGI_H +#define APACHE_UTIL_FCGI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A structure that represents the fixed header fields + * at the beginning of a "FastCGI record" (i.e., the data prior + * to content data and padding). + */ +typedef struct { + /** See values for version, below */ + unsigned char version; + /** See values for type, below */ + unsigned char type; + /** request id, in two parts */ + unsigned char requestIdB1; + unsigned char requestIdB0; + /** content length, in two parts */ + unsigned char contentLengthB1; + unsigned char contentLengthB0; + /** padding length */ + unsigned char paddingLength; + /** 8-bit reserved field */ + unsigned char reserved; +} ap_fcgi_header; + +/* + * Number of bytes in the header portion of a "FastCGI record" + * (i.e., ap_fcgi_header structure). Future versions of the + * protocol may increase the size. + */ +#define AP_FCGI_HEADER_LEN 8 + +/** + * Possible values for the version field of ap_fcgi_header + */ +#define AP_FCGI_VERSION_1 1 + +/** + * Possible values for the type field of ap_fcgi_header + */ +#define AP_FCGI_BEGIN_REQUEST 1 +#define AP_FCGI_ABORT_REQUEST 2 +#define AP_FCGI_END_REQUEST 3 +#define AP_FCGI_PARAMS 4 +#define AP_FCGI_STDIN 5 +#define AP_FCGI_STDOUT 6 +#define AP_FCGI_STDERR 7 +#define AP_FCGI_DATA 8 +#define AP_FCGI_GET_VALUES 9 +#define AP_FCGI_GET_VALUES_RESULT 10 +#define AP_FCGI_UNKNOWN_TYPE 11 +#define AP_FCGI_MAXTYPE (AP_FCGI_UNKNOWN_TYPE) + +/** + * Offsets of the various fields of ap_fcgi_header + */ +#define AP_FCGI_HDR_VERSION_OFFSET 0 +#define AP_FCGI_HDR_TYPE_OFFSET 1 +#define AP_FCGI_HDR_REQUEST_ID_B1_OFFSET 2 +#define AP_FCGI_HDR_REQUEST_ID_B0_OFFSET 3 +#define AP_FCGI_HDR_CONTENT_LEN_B1_OFFSET 4 +#define AP_FCGI_HDR_CONTENT_LEN_B0_OFFSET 5 +#define AP_FCGI_HDR_PADDING_LEN_OFFSET 6 +#define AP_FCGI_HDR_RESERVED_OFFSET 7 + +/** + * @brief This represents the content data of the FastCGI record when + * the type is AP_FCGI_BEGIN_REQUEST. + */ +typedef struct { + /** + * role, in two parts + * See values for role, below + */ + unsigned char roleB1; + unsigned char roleB0; + /** + * flags + * See values for flags bits, below + */ + unsigned char flags; + /** reserved */ + unsigned char reserved[5]; +} ap_fcgi_begin_request_body; + +/* + * Values for role component of ap_fcgi_begin_request_body + */ +#define AP_FCGI_RESPONDER 1 +#define AP_FCGI_AUTHORIZER 2 +#define AP_FCGI_FILTER 3 + +/* + * Values for flags bits of ap_fcgi_begin_request_body + */ +#define AP_FCGI_KEEP_CONN 1 /* otherwise the application closes */ + +/** + * Offsets of the various fields of ap_fcgi_begin_request_body + */ +#define AP_FCGI_BRB_ROLEB1_OFFSET 0 +#define AP_FCGI_BRB_ROLEB0_OFFSET 1 +#define AP_FCGI_BRB_FLAGS_OFFSET 2 +#define AP_FCGI_BRB_RESERVED0_OFFSET 3 +#define AP_FCGI_BRB_RESERVED1_OFFSET 4 +#define AP_FCGI_BRB_RESERVED2_OFFSET 5 +#define AP_FCGI_BRB_RESERVED3_OFFSET 6 +#define AP_FCGI_BRB_RESERVED4_OFFSET 7 + +/** + * Serialize ap_fcgi_header + * @param h The header to read from + * @param a The array to write to, of size AP_FCGI_HEADER_LEN + */ +AP_DECLARE(void) ap_fcgi_header_to_array(ap_fcgi_header *h, + unsigned char a[]); + +/** + * Unserialize ap_fcgi_header + * @param h The header to write to + * @param a The array to read from, of size AP_FCGI_HEADER_LEN + */ +AP_DECLARE(void) ap_fcgi_header_from_array(ap_fcgi_header *h, + unsigned char a[]); + +/** + * Serialize ap_fcgi_begin_request_body + * @param h The begin-request body to read from + * @param a The array to write to, of size AP_FCGI_HEADER_LEN + */ +AP_DECLARE(void) ap_fcgi_begin_request_body_to_array(ap_fcgi_begin_request_body *h, + unsigned char a[]); + +/** + * Fill in a FastCGI request header with the required field values. + * @param header The header to fill in + * @param type The type of record + * @param request_id The request id + * @param content_len The amount of content which follows the header + * @param padding_len The amount of padding which follows the content + * + * The header array must be at least AP_FCGI_HEADER_LEN bytes long. + */ +AP_DECLARE(void) ap_fcgi_fill_in_header(ap_fcgi_header *header, + unsigned char type, + apr_uint16_t request_id, + apr_uint16_t content_len, + unsigned char padding_len); + +/** + * Fill in a FastCGI begin request body with the required field values. + * @param brb The begin-request-body to fill in + * @param role AP_FCGI_RESPONDER or other roles + * @param flags 0 or a combination of flags like AP_FCGI_KEEP_CONN + */ +AP_DECLARE(void) ap_fcgi_fill_in_request_body(ap_fcgi_begin_request_body *brb, + int role, + unsigned char flags); + +/** + * Compute the buffer size needed to serialize the provided environment table. + * @param r The request, for logging + * @param env The environment table + */ +AP_DECLARE(apr_size_t) ap_fcgi_encoded_env_len(request_rec *r, + apr_table_t *env, + apr_size_t maxlen); + +/** + * Serialize the provided environment table using a buffer previously + * allocated. + * @param r The request, for logging + * @param env The environment table + * @param buffer A buffer to contain the serialized environment table + * @param buflen The length of the buffer, previously computed by + * ap_fcgi_encoded_env_len() + */ +AP_DECLARE(apr_status_t) ap_fcgi_encode_env(request_rec *r, + apr_table_t *env, + void *buffer, + apr_size_t buflen); + +/** + * String forms for the value of the FCGI_ROLE envvar + */ +#define AP_FCGI_RESPONDER_STR "RESPONDER" +#define AP_FCGI_AUTHORIZER_STR "AUTHORIZER" +#define AP_FCGI_FILTER_STR "FILTER" + +/** + * FastCGI implementations that implement the AUTHORIZER role + * for Apache httpd and allow the application to participate in + * any of the Apache httpd AAA phases typically set the variable + * FCGI_APACHE_ROLE to one of these strings to indicate the + * specific AAA phase. + */ +#define AP_FCGI_APACHE_ROLE_AUTHENTICATOR_STR "AUTHENTICATOR" +#define AP_FCGI_APACHE_ROLE_AUTHORIZER_STR "AUTHORIZER" +#define AP_FCGI_APACHE_ROLE_ACCESS_CHECKER_STR "ACCESS_CHECKER" + +#ifdef __cplusplus +} +#endif + +#endif /* !APACHE_UTIL_FCGI_H */ +/** @} */ Index: include/ap_mmn.h =================================================================== --- include/ap_mmn.h (revision 1512819) +++ include/ap_mmn.h (working copy) @@ -437,6 +437,7 @@ * 20130702.0 (2.5.0-dev) Remove pre_htaccess hook, add open_htaccess hook. * 20130702.1 (2.5.0-dev) Restore AUTH_HANDLED to mod_auth.h * 20130702.2 (2.5.0-dev) Add ap_log_data(), ap_log_rdata(), etc. + * 20130702.3 (2.5.0-dev) Add util_fcgi.h, FastCGI protocol support */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */