1 /* (c) 2007,2008 Andrei Nigmatulin */
2
3 #include "fpm_config.h"
4
5 #include <stdlib.h>
6 #include <string.h>
7 #include <stdio.h>
8
9 #include "php.h"
10 #include "php_main.h"
11 #include "php_ini.h"
12 #include "ext/standard/dl.h"
13
14 #include "fastcgi.h"
15
16 #include "fpm.h"
17 #include "fpm_php.h"
18 #include "fpm_cleanup.h"
19 #include "fpm_worker_pool.h"
20 #include "zlog.h"
21
22 static char **limit_extensions = NULL;
23
fpm_php_zend_ini_alter_master(char * name,int name_length,char * new_value,int new_value_length,int mode,int stage)24 static int fpm_php_zend_ini_alter_master(char *name, int name_length, char *new_value, int new_value_length, int mode, int stage) /* {{{ */
25 {
26 zend_ini_entry *ini_entry;
27 zend_string *duplicate;
28
29 if ((ini_entry = zend_hash_str_find_ptr(EG(ini_directives), name, name_length)) == NULL) {
30 return FAILURE;
31 }
32
33 duplicate = zend_string_init(new_value, new_value_length, 1);
34
35 if (!ini_entry->on_modify
36 || ini_entry->on_modify(ini_entry, duplicate,
37 ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) {
38 ini_entry->value = duplicate;
39 /* when mode == ZEND_INI_USER keep unchanged to allow ZEND_INI_PERDIR (.user.ini) */
40 if (mode == ZEND_INI_SYSTEM) {
41 ini_entry->modifiable = mode;
42 }
43 } else {
44 zend_string_release_ex(duplicate, 1);
45 }
46
47 return SUCCESS;
48 }
49 /* }}} */
50
fpm_php_disable(char * value,int (* zend_disable)(const char *,size_t))51 static void fpm_php_disable(char *value, int (*zend_disable)(const char *, size_t)) /* {{{ */
52 {
53 char *s = 0, *e = value;
54
55 while (*e) {
56 switch (*e) {
57 case ' ':
58 case ',':
59 if (s) {
60 *e = '\0';
61 zend_disable(s, e - s);
62 s = 0;
63 }
64 break;
65 default:
66 if (!s) {
67 s = e;
68 }
69 break;
70 }
71 e++;
72 }
73
74 if (s) {
75 zend_disable(s, e - s);
76 }
77 }
78 /* }}} */
79
80 #define FPM_PHP_INI_ALTERING_ERROR -1
81 #define FPM_PHP_INI_APPLIED 1
82 #define FPM_PHP_INI_EXTENSION_FAILED 0
83 #define FPM_PHP_INI_EXTENSION_LOADED 2
84
fpm_php_apply_defines_ex(struct key_value_s * kv,int mode)85 int fpm_php_apply_defines_ex(struct key_value_s *kv, int mode) /* {{{ */
86 {
87
88 char *name = kv->key;
89 char *value = kv->value;
90 int name_len = strlen(name);
91 int value_len = strlen(value);
92
93 if (!strcmp(name, "extension") && *value) {
94 zval zv;
95 zend_interned_strings_switch_storage(0);
96 php_dl(value, MODULE_PERSISTENT, &zv, 1);
97 zend_interned_strings_switch_storage(1);
98 return Z_TYPE(zv) == IS_TRUE ? FPM_PHP_INI_EXTENSION_LOADED : FPM_PHP_INI_EXTENSION_FAILED;
99 }
100
101 if (fpm_php_zend_ini_alter_master(name, name_len, value, value_len, mode, PHP_INI_STAGE_ACTIVATE) == FAILURE) {
102 return FPM_PHP_INI_ALTERING_ERROR;
103 }
104
105 if (!strcmp(name, "disable_functions") && *value) {
106 zend_disable_functions(value);
107 return FPM_PHP_INI_APPLIED;
108 }
109
110 if (!strcmp(name, "disable_classes") && *value) {
111 char *v = strdup(value);
112 PG(disable_classes) = v;
113 fpm_php_disable(v, zend_disable_class);
114 return FPM_PHP_INI_APPLIED;
115 }
116
117 return FPM_PHP_INI_APPLIED;
118 }
119 /* }}} */
120
fpm_php_apply_defines(struct fpm_worker_pool_s * wp)121 static int fpm_php_apply_defines(struct fpm_worker_pool_s *wp) /* {{{ */
122 {
123 struct key_value_s *kv;
124 int apply_result;
125 bool extension_loaded = false;
126
127 for (kv = wp->config->php_values; kv; kv = kv->next) {
128 apply_result = fpm_php_apply_defines_ex(kv, ZEND_INI_USER);
129 if (apply_result == FPM_PHP_INI_ALTERING_ERROR) {
130 zlog(ZLOG_ERROR, "Unable to set php_value '%s'", kv->key);
131 } else if (apply_result == FPM_PHP_INI_EXTENSION_LOADED) {
132 extension_loaded = true;
133 }
134 }
135
136 for (kv = wp->config->php_admin_values; kv; kv = kv->next) {
137 apply_result = fpm_php_apply_defines_ex(kv, ZEND_INI_SYSTEM);
138 if (apply_result == FPM_PHP_INI_ALTERING_ERROR) {
139 zlog(ZLOG_ERROR, "Unable to set php_admin_value '%s'", kv->key);
140 } else if (apply_result == FPM_PHP_INI_EXTENSION_LOADED) {
141 extension_loaded = true;
142 }
143 }
144
145 if (extension_loaded) {
146 zend_collect_module_handlers();
147 }
148
149 return 0;
150 }
151 /* }}} */
152
fpm_php_set_allowed_clients(struct fpm_worker_pool_s * wp)153 static int fpm_php_set_allowed_clients(struct fpm_worker_pool_s *wp) /* {{{ */
154 {
155 if (wp->listen_address_domain == FPM_AF_INET) {
156 fcgi_set_allowed_clients(wp->config->listen_allowed_clients);
157 }
158 return 0;
159 }
160 /* }}} */
161
162 #if 0 /* Comment out this non used function. It could be used later. */
163 static int fpm_php_set_fcgi_mgmt_vars(struct fpm_worker_pool_s *wp) /* {{{ */
164 {
165 char max_workers[10 + 1]; /* 4294967295 */
166 int len;
167
168 len = sprintf(max_workers, "%u", (unsigned int) wp->config->pm_max_children);
169
170 fcgi_set_mgmt_var("FCGI_MAX_CONNS", sizeof("FCGI_MAX_CONNS")-1, max_workers, len);
171 fcgi_set_mgmt_var("FCGI_MAX_REQS", sizeof("FCGI_MAX_REQS")-1, max_workers, len);
172 return 0;
173 }
174 /* }}} */
175 #endif
176
fpm_php_script_filename(void)177 char *fpm_php_script_filename(void)
178 {
179 return SG(request_info).path_translated;
180 }
181
fpm_php_request_uri(void)182 char *fpm_php_request_uri(void)
183 {
184 return (char *) SG(request_info).request_uri;
185 }
186
fpm_php_request_method(void)187 char *fpm_php_request_method(void)
188 {
189 return (char *) SG(request_info).request_method;
190 }
191
fpm_php_query_string(void)192 char *fpm_php_query_string(void)
193 {
194 return SG(request_info).query_string;
195 }
196
fpm_php_auth_user(void)197 char *fpm_php_auth_user(void)
198 {
199 return SG(request_info).auth_user;
200 }
201
fpm_php_content_length(void)202 size_t fpm_php_content_length(void)
203 {
204 return SG(request_info).content_length;
205 }
206
fpm_php_cleanup(int which,void * arg)207 static void fpm_php_cleanup(int which, void *arg) /* {{{ */
208 {
209 php_module_shutdown();
210 sapi_shutdown();
211 if (limit_extensions) {
212 fpm_worker_pool_free_limit_extensions(limit_extensions);
213 }
214 }
215 /* }}} */
216
fpm_php_soft_quit(void)217 void fpm_php_soft_quit(void)
218 {
219 fcgi_terminate();
220 }
221
fpm_php_init_main(void)222 int fpm_php_init_main(void)
223 {
224 if (0 > fpm_cleanup_add(FPM_CLEANUP_PARENT, fpm_php_cleanup, 0)) {
225 return -1;
226 }
227 return 0;
228 }
229
fpm_php_init_child(struct fpm_worker_pool_s * wp)230 int fpm_php_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
231 {
232 if (0 > fpm_php_apply_defines(wp) ||
233 0 > fpm_php_set_allowed_clients(wp)) {
234 return -1;
235 }
236
237 if (wp->limit_extensions) {
238 /* Take ownership of limit_extensions. */
239 limit_extensions = wp->limit_extensions;
240 wp->limit_extensions = NULL;
241 }
242 return 0;
243 }
244 /* }}} */
245
fpm_php_limit_extensions(char * path)246 int fpm_php_limit_extensions(char *path) /* {{{ */
247 {
248 char **p;
249 size_t path_len;
250
251 if (!path || !limit_extensions) {
252 return 0; /* allowed by default */
253 }
254
255 p = limit_extensions;
256 path_len = strlen(path);
257 while (p && *p) {
258 size_t ext_len = strlen(*p);
259 if (path_len > ext_len) {
260 char *path_ext = path + path_len - ext_len;
261 if (strcmp(*p, path_ext) == 0) {
262 return 0; /* allow as the extension has been found */
263 }
264 }
265 p++;
266 }
267
268
269 zlog(ZLOG_NOTICE, "Access to the script '%s' has been denied (see security.limit_extensions)", path);
270 return 1; /* extension not found: not allowed */
271 }
272 /* }}} */
273
fpm_php_is_key_in_table(zend_string * table,const char * key,size_t key_len)274 bool fpm_php_is_key_in_table(zend_string *table, const char *key, size_t key_len) /* {{{ */
275 {
276 zval *data;
277 zend_string *str;
278
279 ZEND_ASSERT(table);
280 ZEND_ASSERT(key);
281
282 /* inspired from ext/standard/info.c */
283
284 zend_is_auto_global(table);
285
286 /* find the table and ensure it's an array */
287 data = zend_hash_find(&EG(symbol_table), table);
288 if (!data || Z_TYPE_P(data) != IS_ARRAY) {
289 return NULL;
290 }
291
292 ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(data), str) {
293 if (str && zend_string_equals_cstr(str, key, key_len)) {
294 return true;
295 }
296 } ZEND_HASH_FOREACH_END();
297
298 return false;
299 }
300 /* }}} */
301