Download | Plain Text | Line Numbers
/*
* Copyright 2014-2020 Manuel Mausz
*
* Licensed 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_log.h"
#include "http_protocol.h"
#include "apr_strings.h"
#define VERSION "0.9"
#define PHP_MAGIC_TYPE "application/x-httpd-php"
module AP_MODULE_DECLARE_DATA phpfpm_handler_module;
typedef struct {
const char *url;
const char *body_temp_path;
apr_table_t *php_values;
} phpfpm_dir_conf;
static apr_status_t write_body(request_rec *r, apr_file_t *file, apr_off_t *size)
{
apr_status_t rc = OK;
if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)))
return rc;
if (ap_should_client_block(r)) {
char argsbuffer[HUGE_STRING_LEN];
apr_off_t rsize, len_read, rpos = 0;
apr_off_t length = r->remaining;
apr_size_t written;
while ((len_read = ap_get_client_block(r, argsbuffer,
sizeof(argsbuffer))) > 0) {
if (length > 0 && (rpos + len_read) > length)
rsize = (apr_size_t)length - rpos;
else
rsize = len_read;
rc = apr_file_write_full(file, argsbuffer, (apr_size_t) rsize,
&written);
if (written != rsize || rc != OK)
return APR_ENOSPC;
if (rc != APR_SUCCESS)
return rc;
rpos += rsize;
}
*size = rpos;
}
return rc;
}
static const char *pass_request_body(request_rec *r, phpfpm_dir_conf *conf,
apr_off_t *size)
{
#if APR_HAS_THREADS
apr_os_thread_t tid = apr_os_thread_current();
char *filename = apr_psprintf(r->pool, "%s/php.%pT.XXXXXX",
conf->body_temp_path, &tid);
#else
char *filename = apr_psprintf(r->pool, "%s/php.%" APR_PID_T_FMT ".XXXXXX",
conf->body_temp_path, getpid());
#endif
apr_file_t *file;
apr_status_t rc = apr_file_mktemp(&file, filename,
APR_FOPEN_CREATE | APR_FOPEN_WRITE | APR_FOPEN_TRUNCATE |
APR_FOPEN_DELONCLOSE, r->pool);
if (rc == APR_SUCCESS)
rc = apr_file_perms_set(filename, APR_FPROT_UREAD | APR_FPROT_UWRITE
| APR_FPROT_GREAD | APR_FPROT_WREAD);
if (rc == APR_SUCCESS)
rc = write_body(r, file, size);
return (rc == APR_SUCCESS) ? filename : NULL;
}
#define MAX_SAVED_LENGTHS 3
static char *serialize_table(request_rec *r, const apr_array_header_t *arr)
{
struct {
apr_size_t key_len;
apr_size_t val_len;
} saved_lengths[MAX_SAVED_LENGTHS];
apr_size_t len = 0;
int i;
if (!arr)
return NULL;
const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
if (arr->nelts == 0)
return NULL;
for (i = 0; i < arr->nelts; ++i) {
apr_size_t key_len = strlen(elts[i].key);
apr_size_t val_len = strlen(elts[i].val);
len += key_len + val_len + 2;
if (i < MAX_SAVED_LENGTHS) {
saved_lengths[i].key_len = key_len;
saved_lengths[i].val_len = val_len;
}
}
char *res = apr_palloc(r->pool, len + 1);
char *cp = res;
for (i = 0; i < arr->nelts; ++i) {
apr_size_t key_len, val_len;
if (i < MAX_SAVED_LENGTHS) {
key_len = saved_lengths[i].key_len;
val_len = saved_lengths[i].val_len;
}
else
{
key_len = strlen(elts[i].key);
val_len = strlen(elts[i].val);
}
memcpy(cp, elts[i].key, key_len);
cp += key_len;
*cp = '=';
cp++;
memcpy(cp, elts[i].val, val_len);
cp += val_len;
*cp = '\n';
cp++;
}
*cp = '\0';
return res;
}
static int handler(request_rec *r)
{
if (!r->filename || r->proxyreq
|| (r->handler && strcmp(r->handler, PHP_MAGIC_TYPE)))
return DECLINED;
if (r->finfo.filetype == 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"script '%s' not found or unable to stat", r->filename);
return HTTP_NOT_FOUND;
}
if (r->finfo.filetype == APR_DIR) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"attempt to invoke directory '%s' as script", r->filename);
return HTTP_FORBIDDEN;
}
phpfpm_dir_conf *conf =
ap_get_module_config(r->per_dir_config, &phpfpm_handler_module);
if (!conf->url)
return DECLINED;
/* handle body temp path */
if (conf->body_temp_path) {
const char *filename = NULL;
apr_off_t size;
if (r->method_number == M_POST) {
const char *ct = apr_table_get(r->headers_in, "Content-Type");
if (ct && !strncasecmp(ct, "multipart/form-data", 19))
filename = pass_request_body(r, conf, &size);
}
else if (r->method_number == M_PUT) {
/* HTTP PUT (e.g. webdav) */
filename = pass_request_body(r, conf, &size);
}
if (filename) {
apr_table_setn(r->subprocess_env, "REQUEST_BODY_FILE", filename);
apr_table_set(r->headers_in, "Content-Length",
apr_off_t_toa(r->pool, size));
/*
* mod_proxy_fcgi tries to read the input in order to determine the content-length:
* see https://svn.apache.org/viewvc?view=revision&revision=1884069
* we prevent that by creating an empty input brigade beforehand
*/
apr_bucket_brigade *input_brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, r->pool);
}
}
#if AP_SERVER_MAJORVERSION_NUMBER >= 2 && AP_SERVER_MINORVERSION_NUMBER >= 4 \
&& AP_SERVER_PATCHLEVEL_NUMBER >= 61
r->handler = conf->url;
#else
r->proxyreq = PROXYREQ_REVERSE;
r->filename = apr_pstrcat(r->pool, conf->url, r->filename, NULL);
r->handler = "proxy-server";
#endif
const char *php_values =
serialize_table(r, apr_table_elts(conf->php_values));
if (php_values)
apr_table_setn(r->subprocess_env, "PHP_VALUE", php_values);
return DECLINED;
}
static void *create_dir_config(apr_pool_t *p, char *dummy)
{
phpfpm_dir_conf *conf = apr_palloc(p, sizeof(*conf));
conf->url = NULL;
conf->body_temp_path = NULL;
conf->php_values = apr_table_make(p, 4);
return conf;
}
static void *merge_dir_config(apr_pool_t *p, void *base_, void *add_)
{
phpfpm_dir_conf *base = (phpfpm_dir_conf *)base_;
phpfpm_dir_conf *add = (phpfpm_dir_conf *)add_;
phpfpm_dir_conf *conf = apr_pcalloc(p, sizeof(*conf));
conf->url = (add->url == NULL) ? base->url : add->url;
conf->body_temp_path = (add->body_temp_path == NULL) ? base->body_temp_path
: add->body_temp_path;
conf->php_values = apr_table_overlay(p, base->php_values, add->php_values);
apr_table_compress(conf->php_values, APR_OVERLAP_TABLES_SET);
return conf;
}
static const char *add_phpfpm_url(cmd_parms *cmd, void *conf_,
const char *arg)
{
phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
conf->url = arg;
return NULL;
}
static const char *add_php_value(cmd_parms *cmd, void *conf_,
const char *name, const char *value)
{
phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
apr_table_setn(conf->php_values, name, value);
return NULL;
}
static const char *add_phpfpm_body_temp_path(cmd_parms *cmd, void *conf_,
const char *arg)
{
phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
conf->body_temp_path = arg;
return NULL;
}
static const char *add_php_flag(cmd_parms *cmd, void *conf,
const char *name, const char *value)
{
char *bool_val = apr_palloc(cmd->pool, sizeof(char) * 2);
if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0'))
bool_val[0] = '1';
else
bool_val[0] = '0';
bool_val[1] = 0;
return add_php_value(cmd, conf, name, bool_val);
}
static const command_rec config_cmds[] =
{
AP_INIT_TAKE1("phpfpm_url", add_phpfpm_url,
NULL, RSRC_CONF|ACCESS_CONF,
"URL to PHP-FPM."),
AP_INIT_TAKE1("phpfpm_body_temp_path", add_phpfpm_body_temp_path,
NULL, RSRC_CONF|ACCESS_CONF,
"Directory for storing temporary files holding client request"
" bodies."),
AP_INIT_TAKE2("php_value", add_php_value,
NULL, OR_OPTIONS|OR_FILEINFO,
"PHP Value Modifier"),
AP_INIT_TAKE2("php_flag", add_php_flag,
NULL, OR_OPTIONS|OR_FILEINFO,
"PHP Flag Modifier"),
{NULL}
};
static void register_hooks(apr_pool_t *p)
{
static const char * const aszSucc[] = { "mod_proxy.c", NULL };
ap_hook_handler(handler, NULL, aszSucc, APR_HOOK_REALLY_FIRST);
}
AP_DECLARE_MODULE(phpfpm_handler) =
{
STANDARD20_MODULE_STUFF,
create_dir_config, /* create per-dir config structure */
merge_dir_config, /* merge per-dir config structures */
NULL, /* create per-server config structure */
NULL, /* merge per-server config structures */
config_cmds, /* command-table */
register_hooks /* register hooks */
};