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