1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Author: Sascha Schumann <sascha@schumann.cx> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* $Id$ */
20
21 #include "php.h"
22
23 #ifdef HAVE_LIBMM
24
25 #include <unistd.h>
26 #include <mm.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <fcntl.h>
31
32 #include "php_session.h"
33 #include "mod_mm.h"
34 #include "SAPI.h"
35
36 #ifdef ZTS
37 # error mm is not thread-safe
38 #endif
39
40 #define PS_MM_FILE "session_mm_"
41
42 /* For php_uint32 */
43 #include "ext/standard/basic_functions.h"
44
45 /* This list holds all data associated with one session. */
46
47 typedef struct ps_sd {
48 struct ps_sd *next;
49 php_uint32 hv; /* hash value of key */
50 time_t ctime; /* time of last change */
51 void *data;
52 size_t datalen; /* amount of valid data */
53 size_t alloclen; /* amount of allocated memory for data */
54 char key[1]; /* inline key */
55 } ps_sd;
56
57 typedef struct {
58 MM *mm;
59 ps_sd **hash;
60 php_uint32 hash_max;
61 php_uint32 hash_cnt;
62 pid_t owner;
63 } ps_mm;
64
65 static ps_mm *ps_mm_instance = NULL;
66
67 #if 0
68 # define ps_mm_debug(a) printf a
69 #else
70 # define ps_mm_debug(a)
71 #endif
72
ps_sd_hash(const char * data,int len)73 static inline php_uint32 ps_sd_hash(const char *data, int len)
74 {
75 php_uint32 h;
76 const char *e = data + len;
77
78 for (h = 2166136261U; data < e; ) {
79 h *= 16777619;
80 h ^= *data++;
81 }
82
83 return h;
84 }
85
hash_split(ps_mm * data)86 static void hash_split(ps_mm *data)
87 {
88 php_uint32 nmax;
89 ps_sd **nhash;
90 ps_sd **ohash, **ehash;
91 ps_sd *ps, *next;
92
93 nmax = ((data->hash_max + 1) << 1) - 1;
94 nhash = mm_calloc(data->mm, nmax + 1, sizeof(*data->hash));
95
96 if (!nhash) {
97 /* no further memory to expand hash table */
98 return;
99 }
100
101 ehash = data->hash + data->hash_max + 1;
102 for (ohash = data->hash; ohash < ehash; ohash++) {
103 for (ps = *ohash; ps; ps = next) {
104 next = ps->next;
105 ps->next = nhash[ps->hv & nmax];
106 nhash[ps->hv & nmax] = ps;
107 }
108 }
109 mm_free(data->mm, data->hash);
110
111 data->hash = nhash;
112 data->hash_max = nmax;
113 }
114
ps_sd_new(ps_mm * data,const char * key)115 static ps_sd *ps_sd_new(ps_mm *data, const char *key)
116 {
117 php_uint32 hv, slot;
118 ps_sd *sd;
119 int keylen;
120
121 keylen = strlen(key);
122
123 sd = mm_malloc(data->mm, sizeof(ps_sd) + keylen);
124 if (!sd) {
125
126 php_error_docref(NULL, E_WARNING, "mm_malloc failed, avail %ld, err %s", mm_available(data->mm), mm_error());
127 return NULL;
128 }
129
130 hv = ps_sd_hash(key, keylen);
131 slot = hv & data->hash_max;
132
133 sd->ctime = 0;
134 sd->hv = hv;
135 sd->data = NULL;
136 sd->alloclen = sd->datalen = 0;
137
138 memcpy(sd->key, key, keylen + 1);
139
140 sd->next = data->hash[slot];
141 data->hash[slot] = sd;
142
143 data->hash_cnt++;
144
145 if (!sd->next) {
146 if (data->hash_cnt >= data->hash_max) {
147 hash_split(data);
148 }
149 }
150
151 ps_mm_debug(("inserting %s(%p) into slot %d\n", key, sd, slot));
152
153 return sd;
154 }
155
ps_sd_destroy(ps_mm * data,ps_sd * sd)156 static void ps_sd_destroy(ps_mm *data, ps_sd *sd)
157 {
158 php_uint32 slot;
159
160 slot = ps_sd_hash(sd->key, strlen(sd->key)) & data->hash_max;
161
162 if (data->hash[slot] == sd) {
163 data->hash[slot] = sd->next;
164 } else {
165 ps_sd *prev;
166
167 /* There must be some entry before the one we want to delete */
168 for (prev = data->hash[slot]; prev->next != sd; prev = prev->next);
169 prev->next = sd->next;
170 }
171
172 data->hash_cnt--;
173
174 if (sd->data) {
175 mm_free(data->mm, sd->data);
176 }
177
178 mm_free(data->mm, sd);
179 }
180
ps_sd_lookup(ps_mm * data,const char * key,int rw)181 static ps_sd *ps_sd_lookup(ps_mm *data, const char *key, int rw)
182 {
183 php_uint32 hv, slot;
184 ps_sd *ret, *prev;
185
186 hv = ps_sd_hash(key, strlen(key));
187 slot = hv & data->hash_max;
188
189 for (prev = NULL, ret = data->hash[slot]; ret; prev = ret, ret = ret->next) {
190 if (ret->hv == hv && !strcmp(ret->key, key)) {
191 break;
192 }
193 }
194
195 if (ret && rw && ret != data->hash[slot]) {
196 /* Move the entry to the top of the linked list */
197 if (prev) {
198 prev->next = ret->next;
199 }
200
201 ret->next = data->hash[slot];
202 data->hash[slot] = ret;
203 }
204
205 ps_mm_debug(("lookup(%s): ret=%p,hv=%u,slot=%d\n", key, ret, hv, slot));
206
207 return ret;
208 }
209
ps_mm_key_exists(ps_mm * data,const char * key)210 static int ps_mm_key_exists(ps_mm *data, const char *key)
211 {
212 ps_sd *sd;
213
214 if (!key) {
215 return FAILURE;
216 }
217 sd = ps_sd_lookup(data, key, 0);
218 if (sd) {
219 return SUCCESS;
220 }
221 return FAILURE;
222 }
223
224 ps_module ps_mod_mm = {
225 PS_MOD_SID(mm)
226 };
227
228 #define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA()
229
ps_mm_initialize(ps_mm * data,const char * path)230 static int ps_mm_initialize(ps_mm *data, const char *path)
231 {
232 data->owner = getpid();
233 data->mm = mm_create(0, path);
234 if (!data->mm) {
235 return FAILURE;
236 }
237
238 data->hash_cnt = 0;
239 data->hash_max = 511;
240 data->hash = mm_calloc(data->mm, data->hash_max + 1, sizeof(ps_sd *));
241 if (!data->hash) {
242 mm_destroy(data->mm);
243 return FAILURE;
244 }
245
246 return SUCCESS;
247 }
248
ps_mm_destroy(ps_mm * data)249 static void ps_mm_destroy(ps_mm *data)
250 {
251 int h;
252 ps_sd *sd, *next;
253
254 /* This function is called during each module shutdown,
255 but we must not release the shared memory pool, when
256 an Apache child dies! */
257 if (data->owner != getpid()) {
258 return;
259 }
260
261 for (h = 0; h < data->hash_max + 1; h++) {
262 for (sd = data->hash[h]; sd; sd = next) {
263 next = sd->next;
264 ps_sd_destroy(data, sd);
265 }
266 }
267
268 mm_free(data->mm, data->hash);
269 mm_destroy(data->mm);
270 free(data);
271 }
272
PHP_MINIT_FUNCTION(ps_mm)273 PHP_MINIT_FUNCTION(ps_mm)
274 {
275 int save_path_len = strlen(PS(save_path));
276 int mod_name_len = strlen(sapi_module.name);
277 int euid_len;
278 char *ps_mm_path, euid[30];
279 int ret;
280
281 ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1);
282 if (!ps_mm_instance) {
283 return FAILURE;
284 }
285
286 if (!(euid_len = slprintf(euid, sizeof(euid), "%d", geteuid()))) {
287 free(ps_mm_instance);
288 ps_mm_instance = NULL;
289 return FAILURE;
290 }
291
292 /* Directory + '/' + File + Module Name + Effective UID + \0 */
293 ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1);
294
295 memcpy(ps_mm_path, PS(save_path), save_path_len);
296 if (save_path_len && PS(save_path)[save_path_len - 1] != DEFAULT_SLASH) {
297 ps_mm_path[save_path_len] = DEFAULT_SLASH;
298 save_path_len++;
299 }
300 memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1);
301 save_path_len += sizeof(PS_MM_FILE) - 1;
302 memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len);
303 save_path_len += mod_name_len;
304 memcpy(ps_mm_path + save_path_len, euid, euid_len);
305 ps_mm_path[save_path_len + euid_len] = '\0';
306
307 ret = ps_mm_initialize(ps_mm_instance, ps_mm_path);
308
309 efree(ps_mm_path);
310
311 if (ret != SUCCESS) {
312 free(ps_mm_instance);
313 ps_mm_instance = NULL;
314 return FAILURE;
315 }
316
317 php_session_register_module(&ps_mod_mm);
318 return SUCCESS;
319 }
320
PHP_MSHUTDOWN_FUNCTION(ps_mm)321 PHP_MSHUTDOWN_FUNCTION(ps_mm)
322 {
323 if (ps_mm_instance) {
324 ps_mm_destroy(ps_mm_instance);
325 return SUCCESS;
326 }
327 return FAILURE;
328 }
329
PS_OPEN_FUNC(mm)330 PS_OPEN_FUNC(mm)
331 {
332 ps_mm_debug(("open: ps_mm_instance=%p\n", ps_mm_instance));
333
334 if (!ps_mm_instance) {
335 return FAILURE;
336 }
337 PS_SET_MOD_DATA(ps_mm_instance);
338
339 return SUCCESS;
340 }
341
PS_CLOSE_FUNC(mm)342 PS_CLOSE_FUNC(mm)
343 {
344 PS_SET_MOD_DATA(NULL);
345
346 return SUCCESS;
347 }
348
PS_READ_FUNC(mm)349 PS_READ_FUNC(mm)
350 {
351 PS_MM_DATA;
352 ps_sd *sd;
353 int ret = FAILURE;
354
355 mm_lock(data->mm, MM_LOCK_RD);
356
357 /* If there is an ID and strict mode, verify existence */
358 if (PS(use_strict_mode)
359 && ps_mm_key_exists(data, key->val) == FAILURE) {
360 /* key points to PS(id), but cannot change here. */
361 if (key) {
362 efree(PS(id));
363 PS(id) = NULL;
364 }
365 PS(id) = PS(mod)->s_create_sid((void **)&data);
366 if (!PS(id)) {
367 return FAILURE;
368 }
369 if (PS(use_cookies)) {
370 PS(send_cookie) = 1;
371 }
372 php_session_reset_id();
373 PS(session_status) = php_session_active;
374 }
375
376 sd = ps_sd_lookup(data, PS(id)->val, 0);
377 if (sd) {
378 *val = zend_string_init(sd->data, sd->datalen, 0);
379 ret = SUCCESS;
380 }
381
382 mm_unlock(data->mm);
383
384 return ret;
385 }
386
PS_WRITE_FUNC(mm)387 PS_WRITE_FUNC(mm)
388 {
389 PS_MM_DATA;
390 ps_sd *sd;
391
392 mm_lock(data->mm, MM_LOCK_RW);
393
394 sd = ps_sd_lookup(data, key->val, 1);
395 if (!sd) {
396 sd = ps_sd_new(data, key->val);
397 ps_mm_debug(("new entry for %s\n", key->val));
398 }
399
400 if (sd) {
401 if (val->len >= sd->alloclen) {
402 if (data->mm) {
403 mm_free(data->mm, sd->data);
404 }
405 sd->alloclen = val->len + 1;
406 sd->data = mm_malloc(data->mm, sd->alloclen);
407
408 if (!sd->data) {
409 ps_sd_destroy(data, sd);
410 php_error_docref(NULL, E_WARNING, "cannot allocate new data segment");
411 sd = NULL;
412 }
413 }
414 if (sd) {
415 sd->datalen = val->len;
416 memcpy(sd->data, val->val, val->len);
417 time(&sd->ctime);
418 }
419 }
420
421 mm_unlock(data->mm);
422
423 return sd ? SUCCESS : FAILURE;
424 }
425
PS_DESTROY_FUNC(mm)426 PS_DESTROY_FUNC(mm)
427 {
428 PS_MM_DATA;
429 ps_sd *sd;
430
431 mm_lock(data->mm, MM_LOCK_RW);
432
433 sd = ps_sd_lookup(data, key->val, 0);
434 if (sd) {
435 ps_sd_destroy(data, sd);
436 }
437
438 mm_unlock(data->mm);
439
440 return SUCCESS;
441 }
442
PS_GC_FUNC(mm)443 PS_GC_FUNC(mm)
444 {
445 PS_MM_DATA;
446 time_t limit;
447 ps_sd **ohash, **ehash;
448 ps_sd *sd, *next;
449
450 *nrdels = 0;
451 ps_mm_debug(("gc\n"));
452
453 time(&limit);
454
455 limit -= maxlifetime;
456
457 mm_lock(data->mm, MM_LOCK_RW);
458
459 ehash = data->hash + data->hash_max + 1;
460 for (ohash = data->hash; ohash < ehash; ohash++) {
461 for (sd = *ohash; sd; sd = next) {
462 next = sd->next;
463 if (sd->ctime < limit) {
464 ps_mm_debug(("purging %s\n", sd->key));
465 ps_sd_destroy(data, sd);
466 (*nrdels)++;
467 }
468 }
469 }
470
471 mm_unlock(data->mm);
472
473 return SUCCESS;
474 }
475
PS_CREATE_SID_FUNC(mm)476 PS_CREATE_SID_FUNC(mm)
477 {
478 zend_string *sid;
479 int maxfail = 3;
480 PS_MM_DATA;
481
482 do {
483 sid = php_session_create_id((void **)&data);
484 /* Check collision */
485 if (ps_mm_key_exists(data, sid->val) == SUCCESS) {
486 if (sid) {
487 zend_string_release(sid);
488 sid = NULL;
489 }
490 if (!(maxfail--)) {
491 return NULL;
492 }
493 }
494 } while(!sid);
495
496 return sid;
497 }
498
499 #endif
500
501 /*
502 * Local variables:
503 * tab-width: 4
504 * c-basic-offset: 4
505 * End:
506 * vim600: sw=4 ts=4 fdm=marker
507 * vim<600: sw=4 ts=4
508 */
509