/* * 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.7" #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) { /* 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); } } r->proxyreq = PROXYREQ_REVERSE; r->filename = apr_pstrcat(r->pool, conf->url, r->filename, NULL); apr_table_setn(r->notes, "rewrite-proxy", "1"); r->handler = "proxy-server"; 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 */ };