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.7"
  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. /* handle body temp path */
  166. if (conf->body_temp_path) {
  167. const char *filename = NULL;
  168. apr_off_t size;
  169.  
  170. if (r->method_number == M_POST) {
  171. const char *ct = apr_table_get(r->headers_in, "Content-Type");
  172. if (ct && !strncasecmp(ct, "multipart/form-data", 19))
  173. filename = pass_request_body(r, conf, &size);
  174. }
  175. else if (r->method_number == M_PUT) {
  176. /* HTTP PUT (e.g. webdav) */
  177. filename = pass_request_body(r, conf, &size);
  178. }
  179.  
  180. if (filename) {
  181. apr_table_setn(r->subprocess_env, "REQUEST_BODY_FILE", filename);
  182. apr_table_set(r->headers_in, "Content-Length",
  183. apr_off_t_toa(r->pool, size));
  184. /*
  185.   * mod_proxy_fcgi tries to read the input in order to determine the content-length:
  186.   * see https://svn.apache.org/viewvc?view=revision&revision=1884069
  187.   * we prevent that by creating an empty input brigade beforehand
  188.   */
  189. apr_bucket_brigade *input_brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
  190. apr_pool_userdata_setn(input_brigade, "proxy-fcgi-input", NULL, r->pool);
  191. }
  192. }
  193.  
  194. r->proxyreq = PROXYREQ_REVERSE;
  195. r->filename = apr_pstrcat(r->pool, conf->url, r->filename, NULL);
  196. apr_table_setn(r->notes, "rewrite-proxy", "1");
  197. r->handler = "proxy-server";
  198.  
  199. const char *php_values =
  200. serialize_table(r, apr_table_elts(conf->php_values));
  201. if (php_values)
  202. apr_table_setn(r->subprocess_env, "PHP_VALUE", php_values);
  203. }
  204.  
  205. return DECLINED;
  206. }
  207.  
  208. static void *create_dir_config(apr_pool_t *p, char *dummy)
  209. {
  210. phpfpm_dir_conf *conf = apr_palloc(p, sizeof(*conf));
  211. conf->url = NULL;
  212. conf->body_temp_path = NULL;
  213. conf->php_values = apr_table_make(p, 4);
  214. return conf;
  215. }
  216.  
  217. static void *merge_dir_config(apr_pool_t *p, void *base_, void *add_)
  218. {
  219. phpfpm_dir_conf *base = (phpfpm_dir_conf *)base_;
  220. phpfpm_dir_conf *add = (phpfpm_dir_conf *)add_;
  221. phpfpm_dir_conf *conf = apr_pcalloc(p, sizeof(*conf));
  222.  
  223. conf->url = (add->url == NULL) ? base->url : add->url;
  224. conf->body_temp_path = (add->body_temp_path == NULL) ? base->body_temp_path
  225. : add->body_temp_path;
  226. conf->php_values = apr_table_overlay(p, base->php_values, add->php_values);
  227. apr_table_compress(conf->php_values, APR_OVERLAP_TABLES_SET);
  228. return conf;
  229. }
  230.  
  231. static const char *add_phpfpm_url(cmd_parms *cmd, void *conf_,
  232. const char *arg)
  233. {
  234. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  235. conf->url = arg;
  236. return NULL;
  237. }
  238.  
  239. static const char *add_php_value(cmd_parms *cmd, void *conf_,
  240. const char *name, const char *value)
  241. {
  242. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  243. apr_table_setn(conf->php_values, name, value);
  244. return NULL;
  245. }
  246.  
  247. static const char *add_phpfpm_body_temp_path(cmd_parms *cmd, void *conf_,
  248. const char *arg)
  249. {
  250. phpfpm_dir_conf *conf = (phpfpm_dir_conf *)conf_;
  251. conf->body_temp_path = arg;
  252. return NULL;
  253. }
  254.  
  255. static const char *add_php_flag(cmd_parms *cmd, void *conf,
  256. const char *name, const char *value)
  257. {
  258. char *bool_val = apr_palloc(cmd->pool, sizeof(char) * 2);
  259.  
  260. if (!strcasecmp(value, "On") || (value[0] == '1' && value[1] == '\0'))
  261. bool_val[0] = '1';
  262. else
  263. bool_val[0] = '0';
  264. bool_val[1] = 0;
  265.  
  266. return add_php_value(cmd, conf, name, bool_val);
  267. }
  268.  
  269. static const command_rec config_cmds[] =
  270. {
  271. AP_INIT_TAKE1("phpfpm_url", add_phpfpm_url,
  272. NULL, RSRC_CONF|ACCESS_CONF,
  273. "URL to PHP-FPM."),
  274. AP_INIT_TAKE1("phpfpm_body_temp_path", add_phpfpm_body_temp_path,
  275. NULL, RSRC_CONF|ACCESS_CONF,
  276. "Directory for storing temporary files holding client request"
  277. " bodies."),
  278. AP_INIT_TAKE2("php_value", add_php_value,
  279. NULL, OR_OPTIONS|OR_FILEINFO,
  280. "PHP Value Modifier"),
  281. AP_INIT_TAKE2("php_flag", add_php_flag,
  282. NULL, OR_OPTIONS|OR_FILEINFO,
  283. "PHP Flag Modifier"),
  284. {NULL}
  285. };
  286.  
  287. static void register_hooks(apr_pool_t *p)
  288. {
  289. static const char * const aszSucc[] = { "mod_proxy.c", NULL };
  290. ap_hook_handler(handler, NULL, aszSucc, APR_HOOK_REALLY_FIRST);
  291. }
  292.  
  293. AP_DECLARE_MODULE(phpfpm_handler) =
  294. {
  295. STANDARD20_MODULE_STUFF,
  296. create_dir_config, /* create per-dir config structure */
  297. merge_dir_config, /* merge per-dir config structures */
  298. NULL, /* create per-server config structure */
  299. NULL, /* merge per-server config structures */
  300. config_cmds, /* command-table */
  301. register_hooks /* register hooks */
  302. };
  303.