1 | /* |
2 | +----------------------------------------------------------------------+ |
3 | | PHP Version 5 | |
4 | +----------------------------------------------------------------------+ |
5 | | Copyright (c) 1997-2015 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 | |
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 | |
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 | |
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 | TSRMLS_FETCH(); |
126 | |
127 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "mm_malloc failed, avail %ld, err %s" , mm_available(data->mm), mm_error()); |
128 | return NULL; |
129 | } |
130 | |
131 | hv = ps_sd_hash(key, keylen); |
132 | slot = hv & data->hash_max; |
133 | |
134 | sd->ctime = 0; |
135 | sd->hv = hv; |
136 | sd->data = NULL; |
137 | sd->alloclen = sd->datalen = 0; |
138 | |
139 | memcpy(sd->key, key, keylen + 1); |
140 | |
141 | sd->next = data->hash[slot]; |
142 | data->hash[slot] = sd; |
143 | |
144 | data->hash_cnt++; |
145 | |
146 | if (!sd->next) { |
147 | if (data->hash_cnt >= data->hash_max) { |
148 | hash_split(data); |
149 | } |
150 | } |
151 | |
152 | ps_mm_debug(("inserting %s(%p) into slot %d\n" , key, sd, slot)); |
153 | |
154 | return sd; |
155 | } |
156 | |
157 | static void ps_sd_destroy(ps_mm *data, ps_sd *sd) |
158 | { |
159 | php_uint32 slot; |
160 | |
161 | slot = ps_sd_hash(sd->key, strlen(sd->key)) & data->hash_max; |
162 | |
163 | if (data->hash[slot] == sd) { |
164 | data->hash[slot] = sd->next; |
165 | } else { |
166 | ps_sd *prev; |
167 | |
168 | /* There must be some entry before the one we want to delete */ |
169 | for (prev = data->hash[slot]; prev->next != sd; prev = prev->next); |
170 | prev->next = sd->next; |
171 | } |
172 | |
173 | data->hash_cnt--; |
174 | |
175 | if (sd->data) { |
176 | mm_free(data->mm, sd->data); |
177 | } |
178 | |
179 | mm_free(data->mm, sd); |
180 | } |
181 | |
182 | static ps_sd *ps_sd_lookup(ps_mm *data, const char *key, int rw) |
183 | { |
184 | php_uint32 hv, slot; |
185 | ps_sd *ret, *prev; |
186 | |
187 | hv = ps_sd_hash(key, strlen(key)); |
188 | slot = hv & data->hash_max; |
189 | |
190 | for (prev = NULL, ret = data->hash[slot]; ret; prev = ret, ret = ret->next) { |
191 | if (ret->hv == hv && !strcmp(ret->key, key)) { |
192 | break; |
193 | } |
194 | } |
195 | |
196 | if (ret && rw && ret != data->hash[slot]) { |
197 | /* Move the entry to the top of the linked list */ |
198 | if (prev) { |
199 | prev->next = ret->next; |
200 | } |
201 | |
202 | ret->next = data->hash[slot]; |
203 | data->hash[slot] = ret; |
204 | } |
205 | |
206 | ps_mm_debug(("lookup(%s): ret=%p,hv=%u,slot=%d\n" , key, ret, hv, slot)); |
207 | |
208 | return ret; |
209 | } |
210 | |
211 | static int ps_mm_key_exists(ps_mm *data, const char *key TSRMLS_DC) |
212 | { |
213 | ps_sd *sd; |
214 | |
215 | if (!key) { |
216 | return FAILURE; |
217 | } |
218 | sd = ps_sd_lookup(data, key, 0); |
219 | if (sd) { |
220 | return SUCCESS; |
221 | } |
222 | return FAILURE; |
223 | } |
224 | |
225 | ps_module ps_mod_mm = { |
226 | PS_MOD_SID(mm) |
227 | }; |
228 | |
229 | #define PS_MM_DATA ps_mm *data = PS_GET_MOD_DATA() |
230 | |
231 | static int ps_mm_initialize(ps_mm *data, const char *path) |
232 | { |
233 | data->owner = getpid(); |
234 | data->mm = mm_create(0, path); |
235 | if (!data->mm) { |
236 | return FAILURE; |
237 | } |
238 | |
239 | data->hash_cnt = 0; |
240 | data->hash_max = 511; |
241 | data->hash = mm_calloc(data->mm, data->hash_max + 1, sizeof(ps_sd *)); |
242 | if (!data->hash) { |
243 | mm_destroy(data->mm); |
244 | return FAILURE; |
245 | } |
246 | |
247 | return SUCCESS; |
248 | } |
249 | |
250 | static void ps_mm_destroy(ps_mm *data) |
251 | { |
252 | int h; |
253 | ps_sd *sd, *next; |
254 | |
255 | /* This function is called during each module shutdown, |
256 | but we must not release the shared memory pool, when |
257 | an Apache child dies! */ |
258 | if (data->owner != getpid()) { |
259 | return; |
260 | } |
261 | |
262 | for (h = 0; h < data->hash_max + 1; h++) { |
263 | for (sd = data->hash[h]; sd; sd = next) { |
264 | next = sd->next; |
265 | ps_sd_destroy(data, sd); |
266 | } |
267 | } |
268 | |
269 | mm_free(data->mm, data->hash); |
270 | mm_destroy(data->mm); |
271 | free(data); |
272 | } |
273 | |
274 | PHP_MINIT_FUNCTION(ps_mm) |
275 | { |
276 | int save_path_len = strlen(PS(save_path)); |
277 | int mod_name_len = strlen(sapi_module.name); |
278 | int euid_len; |
279 | char *ps_mm_path, euid[30]; |
280 | int ret; |
281 | |
282 | ps_mm_instance = calloc(sizeof(*ps_mm_instance), 1); |
283 | if (!ps_mm_instance) { |
284 | return FAILURE; |
285 | } |
286 | |
287 | if (!(euid_len = slprintf(euid, sizeof(euid), "%d" , geteuid()))) { |
288 | free(ps_mm_instance); |
289 | ps_mm_instance = NULL; |
290 | return FAILURE; |
291 | } |
292 | |
293 | /* Directory + '/' + File + Module Name + Effective UID + \0 */ |
294 | ps_mm_path = emalloc(save_path_len + 1 + (sizeof(PS_MM_FILE) - 1) + mod_name_len + euid_len + 1); |
295 | |
296 | memcpy(ps_mm_path, PS(save_path), save_path_len); |
297 | if (save_path_len && PS(save_path)[save_path_len - 1] != DEFAULT_SLASH) { |
298 | ps_mm_path[save_path_len] = DEFAULT_SLASH; |
299 | save_path_len++; |
300 | } |
301 | memcpy(ps_mm_path + save_path_len, PS_MM_FILE, sizeof(PS_MM_FILE) - 1); |
302 | save_path_len += sizeof(PS_MM_FILE) - 1; |
303 | memcpy(ps_mm_path + save_path_len, sapi_module.name, mod_name_len); |
304 | save_path_len += mod_name_len; |
305 | memcpy(ps_mm_path + save_path_len, euid, euid_len); |
306 | ps_mm_path[save_path_len + euid_len] = '\0'; |
307 | |
308 | ret = ps_mm_initialize(ps_mm_instance, ps_mm_path); |
309 | |
310 | efree(ps_mm_path); |
311 | |
312 | if (ret != SUCCESS) { |
313 | free(ps_mm_instance); |
314 | ps_mm_instance = NULL; |
315 | return FAILURE; |
316 | } |
317 | |
318 | php_session_register_module(&ps_mod_mm); |
319 | return SUCCESS; |
320 | } |
321 | |
322 | PHP_MSHUTDOWN_FUNCTION(ps_mm) |
323 | { |
324 | if (ps_mm_instance) { |
325 | ps_mm_destroy(ps_mm_instance); |
326 | return SUCCESS; |
327 | } |
328 | return FAILURE; |
329 | } |
330 | |
331 | PS_OPEN_FUNC(mm) |
332 | { |
333 | ps_mm_debug(("open: ps_mm_instance=%p\n" , ps_mm_instance)); |
334 | |
335 | if (!ps_mm_instance) { |
336 | return FAILURE; |
337 | } |
338 | PS_SET_MOD_DATA(ps_mm_instance); |
339 | |
340 | return SUCCESS; |
341 | } |
342 | |
343 | PS_CLOSE_FUNC(mm) |
344 | { |
345 | PS_SET_MOD_DATA(NULL); |
346 | |
347 | return SUCCESS; |
348 | } |
349 | |
350 | PS_READ_FUNC(mm) |
351 | { |
352 | PS_MM_DATA; |
353 | ps_sd *sd; |
354 | int ret = FAILURE; |
355 | |
356 | mm_lock(data->mm, MM_LOCK_RD); |
357 | |
358 | /* If there is an ID and strict mode, verify existence */ |
359 | if (PS(use_strict_mode) |
360 | && ps_mm_key_exists(data, key TSRMLS_CC) == FAILURE) { |
361 | /* key points to PS(id), but cannot change here. */ |
362 | if (key) { |
363 | efree(PS(id)); |
364 | PS(id) = NULL; |
365 | } |
366 | PS(id) = PS(mod)->s_create_sid((void **)&data, NULL TSRMLS_CC); |
367 | if (!PS(id)) { |
368 | return FAILURE; |
369 | } |
370 | if (PS(use_cookies)) { |
371 | PS(send_cookie) = 1; |
372 | } |
373 | php_session_reset_id(TSRMLS_C); |
374 | PS(session_status) = php_session_active; |
375 | } |
376 | |
377 | sd = ps_sd_lookup(data, PS(id), 0); |
378 | if (sd) { |
379 | *vallen = sd->datalen; |
380 | *val = emalloc(sd->datalen + 1); |
381 | memcpy(*val, sd->data, sd->datalen); |
382 | (*val)[sd->datalen] = '\0'; |
383 | ret = SUCCESS; |
384 | } |
385 | |
386 | mm_unlock(data->mm); |
387 | |
388 | return ret; |
389 | } |
390 | |
391 | PS_WRITE_FUNC(mm) |
392 | { |
393 | PS_MM_DATA; |
394 | ps_sd *sd; |
395 | |
396 | mm_lock(data->mm, MM_LOCK_RW); |
397 | |
398 | sd = ps_sd_lookup(data, key, 1); |
399 | if (!sd) { |
400 | sd = ps_sd_new(data, key); |
401 | ps_mm_debug(("new entry for %s\n" , key)); |
402 | } |
403 | |
404 | if (sd) { |
405 | if (vallen >= sd->alloclen) { |
406 | if (data->mm) { |
407 | mm_free(data->mm, sd->data); |
408 | } |
409 | sd->alloclen = vallen + 1; |
410 | sd->data = mm_malloc(data->mm, sd->alloclen); |
411 | |
412 | if (!sd->data) { |
413 | ps_sd_destroy(data, sd); |
414 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot allocate new data segment" ); |
415 | sd = NULL; |
416 | } |
417 | } |
418 | if (sd) { |
419 | sd->datalen = vallen; |
420 | memcpy(sd->data, val, vallen); |
421 | time(&sd->ctime); |
422 | } |
423 | } |
424 | |
425 | mm_unlock(data->mm); |
426 | |
427 | return sd ? SUCCESS : FAILURE; |
428 | } |
429 | |
430 | PS_DESTROY_FUNC(mm) |
431 | { |
432 | PS_MM_DATA; |
433 | ps_sd *sd; |
434 | |
435 | mm_lock(data->mm, MM_LOCK_RW); |
436 | |
437 | sd = ps_sd_lookup(data, key, 0); |
438 | if (sd) { |
439 | ps_sd_destroy(data, sd); |
440 | } |
441 | |
442 | mm_unlock(data->mm); |
443 | |
444 | return SUCCESS; |
445 | } |
446 | |
447 | PS_GC_FUNC(mm) |
448 | { |
449 | PS_MM_DATA; |
450 | time_t limit; |
451 | ps_sd **ohash, **ehash; |
452 | ps_sd *sd, *next; |
453 | |
454 | *nrdels = 0; |
455 | ps_mm_debug(("gc\n" )); |
456 | |
457 | time(&limit); |
458 | |
459 | limit -= maxlifetime; |
460 | |
461 | mm_lock(data->mm, MM_LOCK_RW); |
462 | |
463 | ehash = data->hash + data->hash_max + 1; |
464 | for (ohash = data->hash; ohash < ehash; ohash++) { |
465 | for (sd = *ohash; sd; sd = next) { |
466 | next = sd->next; |
467 | if (sd->ctime < limit) { |
468 | ps_mm_debug(("purging %s\n" , sd->key)); |
469 | ps_sd_destroy(data, sd); |
470 | (*nrdels)++; |
471 | } |
472 | } |
473 | } |
474 | |
475 | mm_unlock(data->mm); |
476 | |
477 | return SUCCESS; |
478 | } |
479 | |
480 | PS_CREATE_SID_FUNC(mm) |
481 | { |
482 | char *sid; |
483 | int maxfail = 3; |
484 | PS_MM_DATA; |
485 | |
486 | do { |
487 | sid = php_session_create_id((void **)&data, newlen TSRMLS_CC); |
488 | /* Check collision */ |
489 | if (ps_mm_key_exists(data, sid TSRMLS_CC) == SUCCESS) { |
490 | if (sid) { |
491 | efree(sid); |
492 | sid = NULL; |
493 | } |
494 | if (!(maxfail--)) { |
495 | return NULL; |
496 | } |
497 | } |
498 | } while(!sid); |
499 | |
500 | return sid; |
501 | } |
502 | |
503 | #endif |
504 | |
505 | /* |
506 | * Local variables: |
507 | * tab-width: 4 |
508 | * c-basic-offset: 4 |
509 | * End: |
510 | * vim600: sw=4 ts=4 fdm=marker |
511 | * vim<600: sw=4 ts=4 |
512 | */ |
513 | |