Download | Plain Text | No Line Numbers


  1. /*
  2.  * Copyright 2014-2020 Manuel Mausz
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  * http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  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.
  15. */
  16. #include "httpd.h"
  17. #include "http_log.h"
  18. #include "http_protocol.h"
  19. #include "apr_strings.h"
  20.  
  21. #define VERSION "0.9"
  22. #define PHP_MAGIC_TYPE "application/x-httpd-php"
  23.  
  24. module AP_MODULE_DECLARE_DATA phpfpm_handler_module;
  25.  
  26. typedef struct {
  27. const char *url;
  28. const char *body_temp_path;
  29. apr_table_t *php_values;
  30. } phpfpm_dir_conf;
  31.  
  32. static apr_status_t write_body(request_rec *r, apr_file_t *file, apr_off_t *size)
  33. {
  34. apr_status_t rc = OK;
  35.  
  36. if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)))
  37. return rc;
  38.  
  39. if (ap_should_client_block(r)) {
  40. char argsbuffer[HUGE_STRING_LEN];
  41. apr_off_t rsize, len_read, rpos = 0;
  42. apr_off_t length = r->remaining;
  43. apr_size_t written;
  44.  
  45. while ((len_read = ap_get_client_block(r, argsbuffer,
  46. sizeof(argsbuffer))) > 0) {
  47. if (length > 0 && (rpos + len_read) > length)
  48. rsize = (apr_size_t)length - rpos;
  49. else
  50. rsize = len_read;
  51.  
  52. rc = apr_file_write_full(file, argsbuffer, (apr_size_t) rsize,
  53. &written);
  54. if (written != rsize || rc != OK)
  55. return APR_ENOSPC;
  56. if (rc != APR_SUCCESS)
  57. return rc;
  58. rpos += rsize;
  59. }
  60.  
  61. *size = rpos;
  62. }
  63.  
  64. return rc;
  65. }
  66.  
  67. static const char *pass_request_body(request_rec *r, phpfpm_dir_conf *conf,
  68. apr_off_t *size)
  69. {
  70. #if APR_HAS_THREADS
  71. apr_os_thread_t tid = apr_os_thread_current();
  72. char *filename = apr_psprintf(r->pool, "%s/php.%pT.XXXXXX",
  73. conf->body_temp_path, &tid);
  74. #else
  75. char *filename = apr_psprintf(r->pool, "%s/php.%" APR_PID_T_FMT ".XXXXXX",
  76. conf->body_temp_path, getpid());
  77. #endif
  78.  
  79. apr_file_t *file;
  80. apr_status_t rc = apr_file_mktemp(&file, filename,
  81. APR_FOPEN_CREATE | APR_FOPEN_WRITE | APR_FOPEN_TRUNCATE |
  82. APR_FOPEN_DELONCLOSE, r->pool);
  83. if (rc == APR_SUCCESS)
  84. rc = apr_file_perms_set(filename, APR_FPROT_UREAD | APR_FPROT_UWRITE
  85. | APR_FPROT_GREAD | APR_FPROT_WREAD);
  86. if (rc == APR_SUCCESS)
  87. rc = write_body(r, file, size);
  88. return (rc == APR_SUCCESS) ? filename : NULL;
  89. }
  90.  
  91. #define MAX_SAVED_LENGTHS 3
  92. static char *serialize_table(request_rec *r, const apr_array_header_t *arr)
  93. {
  94. struct {
  95. apr_size_t key_len;
  96. apr_size_t val_len;
  97. } saved_lengths[MAX_SAVED_LENGTHS];
  98. apr_size_t len = 0;
  99. int i;
  100.  
  101. if (!arr)
  102. return NULL;
  103.  
  104. const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
  105. if (arr->nelts == 0)
  106. return NULL;
  107. for (i = 0; i < arr->nelts; ++i) {
  108. apr_size_t key_len = strlen(elts[i].key);
  109. apr_size_t val_len = strlen(elts[i].val);
  110. len += key_len + val_len + 2;
  111. if (i < MAX_SAVED_LENGTHS) {
  112. saved_lengths[i].key_len = key_len;
  113. saved_lengths[i].val_len = val_len;
  114. }
  115. }
  116.  
  117. char *res = apr_palloc(r->pool, len + 1);
  118. char *cp = res;
  119. for (i = 0; i < arr->nelts; ++i) {
  120. apr_size_t key_len, val_len;
  121. if (i < MAX_SAVED_LENGTHS) {
  122. key_len = saved_lengths[i].key_len;
  123. val_len = saved_lengths[i].val_len;
  124. }
  125. else
  126. {
  127. key_len = strlen(elts[i].key);
  128. val_len = strlen(elts[i].val);
  129. }
  130.  
  131. memcpy(cp, elts[i].key, key_len);
  132. cp += key_len;
  133. *cp = '=';
  134. cp++;
  135. memcpy(cp, elts[i].val, val_len);
  136. cp += val_len;
  137. *cp = '\n';
  138. cp++;
  139. }
  140. *cp = '\0';
  141.  
  142. return res;
  143. }
  144.  
  145. static int handler(request_rec *r)
  146. {
  147. if (!r->filename || r->proxyreq
  148. || (r->handler && strcmp(r->handler, PHP_MAGIC_TYPE)))
  149. return DECLINED;
  150.  
  151. if (r->finfo.filetype == 0) {
  152. ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  153. "script '%s' not found or unable to stat", r->filename);
  154. return HTTP_NOT_FOUND;
  155. }
  156. if (r->finfo.filetype == APR_DIR) {
  157. ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
  158. "attempt to invoke directory '%s' as script", r->filename);
  159. return HTTP_FORBIDDEN;
  160. }
  161.  
  162. phpfpm_dir_conf *conf =
  163. ap_get_module_config(r->per_dir_config, &phpfpm_handler_module);
  164. if (!conf->url)
  165. return DECLINED;
  166.  
  167. /* handle body temp path */
  168. if (conf->body_temp_path) {
  169. const char *filename = NULL;
  170. apr_off_t size;
  171.  
  172. if (r->method_number == M_POST) {
  173. const char *ct = apr_table_get(r->headers_in, "Content-Type");
  174. if (ct && !strncasecmp(ct, "multipart/form-data", 19))
  175. filename = pass_request_body(r, conf, &size);
  176. }
  177. else if (r->method_number == M_PUT) {
  178. /* HTTP PUT (e.g. webdav) */
  179. filename = pass_request_body(r, conf, &size);
  180. }
  181.  
  182. if (filename) {
  183. apr_table_setn(r->subprocess_env, "REQUEST_BODY_FILE", filename);
  184. apr_table_set(r->headers_in, "Content-Length",
  185. apr_off_t_toa(r->pool, size));
  186. /*
  187.   * mod_proxy_fcgi tries to read the input in order to determine the content-length:
  188.   * see https://svn.apache.org/viewvc?view=revision&revision=1884069
  189.   * we prevent that by creating an empty input brigade beforehand
  190.   */
  191. apr_bucket_brigade *input_brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
  192. apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, r->pool);
  193. }
  194. }
  195.  
  196. #if AP_SERVER_MAJORVERSION_NUMBER >= 2 && AP_SERVER_MINORVERSION_NUMBER >= 4 \
  197.   && AP_SERVER_PATCHLEVEL_NUMBER >= 61
  198. r->handler = conf->url;
  199. #else
  200. r->proxyreq = PROXYREQ_REVERSE;
  201. r->filename = apr_pstrcat(r->pool, conf->url, r->filename, NULL);
  202. r->handler = "proxy-server";
  203. #endif
  204.  
  205. const char *php_values =
  206. serialize_table(r, apr_table_elts(conf->php_values));
  207. if (php_values)
  208. apr_table_setn(r->subprocess_env, "PHP_VALUE", php_values);
  209.  
  210. return DECLINED;
  211. }
  212.  
  213. static void *create_dir_config(apr_pool_t *p, char *dummy)
  214. {
  215. phpfpm_dir_conf *conf = apr_palloc(p, sizeof(*conf));
  216. conf->url = NULL;
  217. conf->body_temp_path = NULL;
  218. conf->php_values = apr_table_make(p, 4);
  219. return conf;
  220. }
  221.  
  222. static void *merge_dir_config(apr_pool_t *p, void *base_, void *add_)
  223. {
  224. phpfpm_dir_conf *base = (phpfpm_dir_conf *)base_;
  225. phpfpm_dir_conf *add = (phpfpm_dir_conf *)add_;
  226. phpfpm_dir_conf *conf = apr_pcalloc(p, sizeof(*conf));
  227.  
  228. conf->url = (add->url == NULL) ? base->url : add->url;
  229. conf->body_temp_path = (add->body_temp_path == NULL) ? base->body_temp_path
  230. : add->body_temp_path;
  231. conf->php_values = apr_table_overlay(p, base->php_values, add->php_values);
  232. apr_table_compress(conf->php_values, APR_OVERLAP_TABLES_SET);
  233. return conf;
  234. }
  235.  
  236. static const char *add_phpfpm_url(cmd_parms *cmd, void *conf_,
  237. const char *arg)
  238. {
  239. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  240. conf->url = arg;
  241. return NULL;
  242. }
  243.  
  244. static const char *add_php_value(cmd_parms *cmd, void *conf_,
  245. const char *name, const char *value)
  246. {
  247. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  248. apr_table_setn(conf->php_values, name, value);
  249. return NULL;
  250. }
  251.  
  252. static const char *add_phpfpm_body_temp_path(cmd_parms *cmd, void *conf_,
  253. const char *arg)
  254. {
  255. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  256. conf->body_temp_path = arg;
  257. return NULL;
  258. }
  259.  
  260. static const char *add_php_flag(cmd_parms *cmd, void *conf,
  261. const char *name, const char *value)
  262. {
  263. char *bool_val = apr_palloc(cmd->pool, sizeof(char) * 2);
  264.  
  265. if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0'))
  266. bool_val[0] = '1';
  267. else
  268. bool_val[0] = '0';
  269. bool_val[1] = 0;
  270.  
  271. return add_php_value(cmd, conf, name, bool_val);
  272. }
  273.  
  274. static const command_rec config_cmds[] =
  275. {
  276. AP_INIT_TAKE1("phpfpm_url", add_phpfpm_url,
  277. NULL, RSRC_CONF|ACCESS_CONF,
  278. "URL to PHP-FPM."),
  279. AP_INIT_TAKE1("phpfpm_body_temp_path", add_phpfpm_body_temp_path,
  280. NULL, RSRC_CONF|ACCESS_CONF,
  281. "Directory for storing temporary files holding client request"
  282. " bodies."),
  283. AP_INIT_TAKE2("php_value", add_php_value,
  284. NULL, OR_OPTIONS|OR_FILEINFO,
  285. "PHP Value Modifier"),
  286. AP_INIT_TAKE2("php_flag", add_php_flag,
  287. NULL, OR_OPTIONS|OR_FILEINFO,
  288. "PHP Flag Modifier"),
  289. {NULL}
  290. };
  291.  
  292. static void register_hooks(apr_pool_t *p)
  293. {
  294. static const char * const aszSucc[] = { "mod_proxy.c", NULL };
  295. ap_hook_handler(handler, NULL, aszSucc, APR_HOOK_REALLY_FIRST);
  296. }
  297.  
  298. AP_DECLARE_MODULE(phpfpm_handler) =
  299. {
  300. STANDARD20_MODULE_STUFF,
  301. create_dir_config, /* create per-dir config structure */
  302. merge_dir_config, /* merge per-dir config structures */
  303. NULL, /* create per-server config structure */
  304. NULL, /* merge per-server config structures */
  305. config_cmds, /* command-table */
  306. register_hooks /* register hooks */
  307. };
  308.