xref: /PHP-8.0/ext/session/mod_files.c (revision f293e6b9)
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 /**************************************************************************
18  * Files save handler should be used as reference implementations of session
19  * save handlers. PS_* functions are called as follows with standard usage.
20  *
21  *    PS_OPEN_FUNC()  - Create module data that manages save handler.
22  *    PS_CREATE_SID() and/or PS_VALIDATE_SID()
23  *                    - PS_CREATE_ID() is called if session ID(key) is not
24  *                      provided or invalid. PS_VALIDATE_SID() is called to
25  *                      verify session ID already exists or not to mitigate
26  *                      session adoption vulnerability risk.
27  *    PS_READ_FUNC()  - Read data from storage.
28  *    PS_GC_FUNC()    - Perform GC. Called by probability
29  *                                (session.gc_probability/session.gc_divisor).
30  *    PS_WRITE_FUNC() or PS_UPDATE_TIMESTAMP()
31  *                    - Write session data or update session data timestamp.
32  *                      It depends on session data change.
33  *    PS_CLOSE_FUNC() - Clean up module data created by PS_OPEN_FUNC().
34  *
35  * Session module guarantees PS_OPEN_FUNC() is called before calling other
36  * PS_*_FUNC() functions. Other than this, session module may call any
37  * PS_*_FUNC() at any time. You may assume non null *mod_data created by
38  * PS_OPEN_FUNC() is passed to PS_*_FUNC().
39  *
40  * NOTE:
41  *  - Save handlers _MUST_NOT_ change/refer PS() values.
42  *    i.e. PS(id), PS(session_status), PS(mod) and any other PS() values.
43  *    Use only function parameters passed from session module.
44  *  - Save handler _MUST_ use PS_GET_MOD_DATA()/PS_SET_MOD_DATA() macro to
45  *    set/get save handler module data(mod_data). mod_data contains
46  *    data required by PS modules. It will not be NULL except PS_OPEN_FUNC().
47  *  - Refer to PS_* macros in php_session.h for function/parameter definitions.
48  *  - Returning FAILURE state from PS_* function results in raising errors.
49  *    Avoid failure state as much as possible.
50  *  - Use static ps_[module name]_[function name] functions for internal use.
51  *************************************************************************/
52 
53 #include "php.h"
54 
55 #include <sys/stat.h>
56 #include <sys/types.h>
57 
58 #ifdef HAVE_SYS_FILE_H
59 #include <sys/file.h>
60 #endif
61 
62 #ifdef HAVE_DIRENT_H
63 #include <dirent.h>
64 #endif
65 
66 #ifdef PHP_WIN32
67 #include "win32/readdir.h"
68 #endif
69 #include <time.h>
70 
71 #include <fcntl.h>
72 #include <errno.h>
73 
74 #ifdef HAVE_UNISTD_H
75 #include <unistd.h>
76 #endif
77 
78 #include "php_session.h"
79 #include "mod_files.h"
80 #include "ext/standard/flock_compat.h"
81 #include "php_open_temporary_file.h"
82 
83 #define FILE_PREFIX "sess_"
84 
85 #ifdef PHP_WIN32
86 # ifndef O_NOFOLLOW
87 #  define O_NOFOLLOW 0
88 # endif
89 #endif
90 
91 typedef struct {
92 	char *lastkey;
93 	char *basedir;
94 	size_t basedir_len;
95 	size_t dirdepth;
96 	size_t st_size;
97 	int filemode;
98 	int fd;
99 } ps_files;
100 
101 const ps_module ps_mod_files = {
102 	/* New save handlers MUST use PS_MOD_UPDATE_TIMESTAMP macro */
103 	PS_MOD_UPDATE_TIMESTAMP(files)
104 };
105 
106 
ps_files_path_create(char * buf,size_t buflen,ps_files * data,const char * key)107 static char *ps_files_path_create(char *buf, size_t buflen, ps_files *data, const char *key)
108 {
109 	size_t key_len;
110 	const char *p;
111 	int i;
112 	size_t n;
113 
114 	key_len = strlen(key);
115 	if (!data || key_len <= data->dirdepth ||
116 		buflen < (strlen(data->basedir) + 2 * data->dirdepth + key_len + 5 + sizeof(FILE_PREFIX))) {
117 		return NULL;
118 	}
119 
120 	p = key;
121 	memcpy(buf, data->basedir, data->basedir_len);
122 	n = data->basedir_len;
123 	buf[n++] = PHP_DIR_SEPARATOR;
124 	for (i = 0; i < (int)data->dirdepth; i++) {
125 		buf[n++] = *p++;
126 		buf[n++] = PHP_DIR_SEPARATOR;
127 	}
128 	memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
129 	n += sizeof(FILE_PREFIX) - 1;
130 	memcpy(buf + n, key, key_len);
131 	n += key_len;
132 	buf[n] = '\0';
133 
134 	return buf;
135 }
136 
137 #ifndef O_BINARY
138 # define O_BINARY 0
139 #endif
140 
ps_files_close(ps_files * data)141 static void ps_files_close(ps_files *data)
142 {
143 	if (data->fd != -1) {
144 #ifdef PHP_WIN32
145 		/* On Win32 locked files that are closed without being explicitly unlocked
146 		   will be unlocked only when "system resources become available". */
147 		flock(data->fd, LOCK_UN);
148 #endif
149 		close(data->fd);
150 		data->fd = -1;
151 	}
152 }
153 
ps_files_open(ps_files * data,const char * key)154 static void ps_files_open(ps_files *data, const char *key)
155 {
156 	char buf[MAXPATHLEN];
157 #if !defined(O_NOFOLLOW) || !defined(PHP_WIN32)
158     struct stat sbuf;
159 #endif
160 	int ret;
161 
162 	if (data->fd < 0 || !data->lastkey || strcmp(key, data->lastkey)) {
163 		if (data->lastkey) {
164 			efree(data->lastkey);
165 			data->lastkey = NULL;
166 		}
167 
168 		ps_files_close(data);
169 
170 		if (php_session_valid_key(key) == FAILURE) {
171 			php_error_docref(NULL, E_WARNING, "Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, \"-\", and \",\" characters are allowed");
172 			return;
173 		}
174 
175 		if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
176 			php_error_docref(NULL, E_WARNING, "Failed to create session data file path. Too short session ID, invalid save_path or path length exceeds %d characters", MAXPATHLEN);
177 			return;
178 		}
179 
180 		data->lastkey = estrdup(key);
181 
182 		/* O_NOFOLLOW to prevent us from following evil symlinks */
183 #ifdef O_NOFOLLOW
184 		data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY | O_NOFOLLOW, data->filemode);
185 #else
186 		/* Check to make sure that the opened file is not outside of allowable dirs.
187 		   This is not 100% safe but it's hard to do something better without O_NOFOLLOW */
188 		if(PG(open_basedir) && lstat(buf, &sbuf) == 0 && S_ISLNK(sbuf.st_mode) && php_check_open_basedir(buf)) {
189 			return;
190 		}
191 		data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY, data->filemode);
192 #endif
193 
194 		if (data->fd != -1) {
195 #ifndef PHP_WIN32
196 			/* check that this session file was created by us or root – we
197 			   don't want to end up accepting the sessions of another webapp
198 
199 			   If the process is ran by root, we ignore session file ownership
200 			   Use case: session is initiated by Apache under non-root and then
201 			   accessed by backend with root permissions to execute some system tasks.
202 
203 			   */
204 			if (zend_fstat(data->fd, &sbuf) || (sbuf.st_uid != 0 && sbuf.st_uid != getuid() && sbuf.st_uid != geteuid() && getuid() != 0)) {
205 				close(data->fd);
206 				data->fd = -1;
207 				php_error_docref(NULL, E_WARNING, "Session data file is not created by your uid");
208 				return;
209 			}
210 #endif
211 			do {
212 				ret = flock(data->fd, LOCK_EX);
213 			} while (ret == -1 && errno == EINTR);
214 
215 #ifdef F_SETFD
216 # ifndef FD_CLOEXEC
217 #  define FD_CLOEXEC 1
218 # endif
219 			if (fcntl(data->fd, F_SETFD, FD_CLOEXEC)) {
220 				php_error_docref(NULL, E_WARNING, "fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)", data->fd, strerror(errno), errno);
221 			}
222 #endif
223 		} else {
224 			php_error_docref(NULL, E_WARNING, "open(%s, O_RDWR) failed: %s (%d)", buf, strerror(errno), errno);
225 		}
226 	}
227 }
228 
ps_files_write(ps_files * data,zend_string * key,zend_string * val)229 static int ps_files_write(ps_files *data, zend_string *key, zend_string *val)
230 {
231 	size_t n = 0;
232 
233 	/* PS(id) may be changed by calling session_regenerate_id().
234 	   Re-initialization should be tried here. ps_files_open() checks
235        data->lastkey and reopen when it is needed. */
236 	ps_files_open(data, ZSTR_VAL(key));
237 	if (data->fd < 0) {
238 		return FAILURE;
239 	}
240 
241 	/* Truncate file if the amount of new data is smaller than the existing data set. */
242 	if (ZSTR_LEN(val) < data->st_size) {
243 		php_ignore_value(ftruncate(data->fd, 0));
244 	}
245 
246 #ifdef HAVE_PWRITE
247 	n = pwrite(data->fd, ZSTR_VAL(val), ZSTR_LEN(val), 0);
248 #else
249 	lseek(data->fd, 0, SEEK_SET);
250 #ifdef PHP_WIN32
251 	{
252 		unsigned int to_write = ZSTR_LEN(val) > UINT_MAX ? UINT_MAX : (unsigned int)ZSTR_LEN(val);
253 		char *buf = ZSTR_VAL(val);
254 		int wrote;
255 
256 		do {
257 			wrote = _write(data->fd, buf, to_write);
258 
259 			n += wrote;
260 			buf = wrote > -1 ? buf + wrote : 0;
261 			to_write = wrote > -1 ? (ZSTR_LEN(val) - n > UINT_MAX ? UINT_MAX : (unsigned int)(ZSTR_LEN(val) - n)): 0;
262 
263 		} while(wrote > 0);
264 	}
265 #else
266 	n = write(data->fd, ZSTR_VAL(val), ZSTR_LEN(val));
267 #endif
268 #endif
269 
270 	if (n != ZSTR_LEN(val)) {
271 		if (n == (size_t)-1) {
272 			php_error_docref(NULL, E_WARNING, "Write failed: %s (%d)", strerror(errno), errno);
273 		} else {
274 			php_error_docref(NULL, E_WARNING, "Write wrote less bytes than requested");
275 		}
276 		return FAILURE;
277 	}
278 
279 	return SUCCESS;
280 }
281 
ps_files_cleanup_dir(const char * dirname,zend_long maxlifetime)282 static int ps_files_cleanup_dir(const char *dirname, zend_long maxlifetime)
283 {
284 	DIR *dir;
285 	struct dirent *entry;
286 	zend_stat_t sbuf;
287 	char buf[MAXPATHLEN];
288 	time_t now;
289 	int nrdels = 0;
290 	size_t dirname_len;
291 
292 	dir = opendir(dirname);
293 	if (!dir) {
294 		php_error_docref(NULL, E_NOTICE, "ps_files_cleanup_dir: opendir(%s) failed: %s (%d)", dirname, strerror(errno), errno);
295 		return (0);
296 	}
297 
298 	time(&now);
299 
300 	dirname_len = strlen(dirname);
301 
302 	if (dirname_len >= MAXPATHLEN) {
303 		php_error_docref(NULL, E_NOTICE, "ps_files_cleanup_dir: dirname(%s) is too long", dirname);
304 		closedir(dir);
305 		return (0);
306 	}
307 
308 	/* Prepare buffer (dirname never changes) */
309 	memcpy(buf, dirname, dirname_len);
310 	buf[dirname_len] = PHP_DIR_SEPARATOR;
311 
312 	while ((entry = readdir(dir))) {
313 		/* does the file start with our prefix? */
314 		if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
315 			size_t entry_len = strlen(entry->d_name);
316 
317 			/* does it fit into our buffer? */
318 			if (entry_len + dirname_len + 2 < MAXPATHLEN) {
319 				/* create the full path.. */
320 				memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
321 
322 				/* NUL terminate it and */
323 				buf[dirname_len + entry_len + 1] = '\0';
324 
325 				/* check whether its last access was more than maxlifetime ago */
326 				if (VCWD_STAT(buf, &sbuf) == 0 &&
327 						(now - sbuf.st_mtime) > maxlifetime) {
328 					VCWD_UNLINK(buf);
329 					nrdels++;
330 				}
331 			}
332 		}
333 	}
334 
335 	closedir(dir);
336 
337 	return (nrdels);
338 }
339 
ps_files_key_exists(ps_files * data,const char * key)340 static int ps_files_key_exists(ps_files *data, const char *key)
341 {
342 	char buf[MAXPATHLEN];
343 	zend_stat_t sbuf;
344 
345 	if (!key || !ps_files_path_create(buf, sizeof(buf), data, key)) {
346 		return FAILURE;
347 	}
348 	if (VCWD_STAT(buf, &sbuf)) {
349 		return FAILURE;
350 	}
351 	return SUCCESS;
352 }
353 
354 
355 #define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()
356 
357 
358 /*
359  * Open save handler. Setup resources that are needed by the handler.
360  * PARAMETERS: PS_OPEN_ARGS in php_session.h
361  * RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL valid module data
362  * (void **mod_data) with SUCCESS, NULL(default) for FAILUREs.
363  *
364  * Files save handler checks/create save_path directory and setup ps_files data.
365  * Note that files save handler supports splitting session data into multiple
366  * directories.
367  * *mod_data, *save_path, *session_name are guaranteed to have non-NULL values.
368  */
PS_OPEN_FUNC(files)369 PS_OPEN_FUNC(files)
370 {
371 	ps_files *data;
372 	const char *p, *last;
373 	const char *argv[3];
374 	int argc = 0;
375 	size_t dirdepth = 0;
376 	int filemode = 0600;
377 
378 	if (*save_path == '\0') {
379 		/* if save path is an empty string, determine the temporary dir */
380 		save_path = php_get_temporary_directory();
381 
382 		if (php_check_open_basedir(save_path)) {
383 			return FAILURE;
384 		}
385 	}
386 
387 	/* split up input parameter */
388 	last = save_path;
389 	p = strchr(save_path, ';');
390 	while (p) {
391 		argv[argc++] = last;
392 		last = ++p;
393 		p = strchr(p, ';');
394 		if (argc > 1) break;
395 	}
396 	argv[argc++] = last;
397 
398 	if (argc > 1) {
399 		errno = 0;
400 		dirdepth = (size_t) ZEND_STRTOL(argv[0], NULL, 10);
401 		if (errno == ERANGE) {
402 			php_error(E_WARNING, "The first parameter in session.save_path is invalid");
403 			return FAILURE;
404 		}
405 	}
406 
407 	if (argc > 2) {
408 		errno = 0;
409 		filemode = (int)ZEND_STRTOL(argv[1], NULL, 8);
410 		if (errno == ERANGE || filemode < 0 || filemode > 07777) {
411 			php_error(E_WARNING, "The second parameter in session.save_path is invalid");
412 			return FAILURE;
413 		}
414 	}
415 	save_path = argv[argc - 1];
416 
417 	data = ecalloc(1, sizeof(*data));
418 
419 	data->fd = -1;
420 	data->dirdepth = dirdepth;
421 	data->filemode = filemode;
422 	data->basedir_len = strlen(save_path);
423 	data->basedir = estrndup(save_path, data->basedir_len);
424 
425 	if (PS_GET_MOD_DATA()) {
426 		ps_close_files(mod_data);
427 	}
428 	PS_SET_MOD_DATA(data);
429 
430 	return SUCCESS;
431 }
432 
433 
434 /*
435  * Clean up opened resources.
436  * PARAMETERS: PS_CLOSE_ARGS in php_session.h
437  * RETURN VALUE: SUCCESS. Must set PS module data(void **mod_data) to NULL.
438  *
439  * Files save handler closes open files and it's memory.
440  * *mod_data is guaranteed to have non-NULL value.
441  * PS_CLOSE_FUNC() must set *mod_data to NULL. PS_CLOSE_FUNC() should not
442  * fail.
443  */
PS_CLOSE_FUNC(files)444 PS_CLOSE_FUNC(files)
445 {
446 	PS_FILES_DATA;
447 
448 	ps_files_close(data);
449 
450 	if (data->lastkey) {
451 		efree(data->lastkey);
452 		data->lastkey = NULL;
453 	}
454 
455 	efree(data->basedir);
456 	efree(data);
457 	PS_SET_MOD_DATA(NULL);
458 
459 	return SUCCESS;
460 }
461 
462 
463 /*
464  * Read session data from opened resource.
465  * PARAMETERS: PS_READ_ARGS in php_session.h
466  * RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL session data to (zend_string **val)
467  * for SUCCESS. NULL(default) for FAILUREs.
468  *
469  * Files save handler supports splitting session data into multiple
470  * directories.
471  * *mod_data, *key are guaranteed to have non-NULL values.
472  */
PS_READ_FUNC(files)473 PS_READ_FUNC(files)
474 {
475 	zend_long n = 0;
476 	zend_stat_t sbuf;
477 	PS_FILES_DATA;
478 
479 	ps_files_open(data, ZSTR_VAL(key));
480 	if (data->fd < 0) {
481 		return FAILURE;
482 	}
483 
484 	if (zend_fstat(data->fd, &sbuf)) {
485 		return FAILURE;
486 	}
487 
488 	data->st_size = sbuf.st_size;
489 
490 	if (sbuf.st_size == 0) {
491 		*val = ZSTR_EMPTY_ALLOC();
492 		return SUCCESS;
493 	}
494 
495 	*val = zend_string_alloc(sbuf.st_size, 0);
496 
497 #ifdef HAVE_PREAD
498 	n = pread(data->fd, ZSTR_VAL(*val), ZSTR_LEN(*val), 0);
499 #else
500 	lseek(data->fd, 0, SEEK_SET);
501 #ifdef PHP_WIN32
502 	{
503 		unsigned int to_read = ZSTR_LEN(*val) > UINT_MAX ? UINT_MAX : (unsigned int)ZSTR_LEN(*val);
504 		char *buf = ZSTR_VAL(*val);
505 		int read_in;
506 
507 		do {
508 			read_in = _read(data->fd, buf, to_read);
509 
510 			n += read_in;
511 			buf = read_in > -1 ? buf + read_in : 0;
512 			to_read = read_in > -1 ? (ZSTR_LEN(*val) - n > UINT_MAX ? UINT_MAX : (unsigned int)(ZSTR_LEN(*val) - n)): 0;
513 
514 		} while(read_in > 0);
515 
516 	}
517 #else
518 	n = read(data->fd, ZSTR_VAL(*val), ZSTR_LEN(*val));
519 #endif
520 #endif
521 
522 	if (n != (zend_long)sbuf.st_size) {
523 		if (n == -1) {
524 			php_error_docref(NULL, E_WARNING, "Read failed: %s (%d)", strerror(errno), errno);
525 		} else {
526 			php_error_docref(NULL, E_WARNING, "Read returned less bytes than requested");
527 		}
528 		zend_string_release_ex(*val, 0);
529 		*val =  ZSTR_EMPTY_ALLOC();
530 		return FAILURE;
531 	}
532 
533 	ZSTR_VAL(*val)[ZSTR_LEN(*val)] = '\0';
534 	return SUCCESS;
535 }
536 
537 
538 /*
539  * Write session data.
540  * PARAMETERS: PS_WRITE_ARGS in php_session.h
541  * RETURN VALUE: SUCCESS or FAILURE.
542  *
543  * PS_WRITE_FUNC() must write session data(zend_string *val) unconditionally.
544  * *mod_data, *key, *val are guaranteed to have non-NULL values.
545  */
PS_WRITE_FUNC(files)546 PS_WRITE_FUNC(files)
547 {
548 	PS_FILES_DATA;
549 
550 	return ps_files_write(data, key, val);
551 }
552 
553 
554 /*
555  * Update session data modification/access time stamp.
556  * PARAMETERS: PS_UPDATE_TIMESTAMP_ARGS in php_session.h
557  * RETURN VALUE: SUCCESS or FAILURE.
558  *
559  * PS_UPDATE_TIMESTAMP_FUNC() updates time stamp(mtime) so that active session
560  * data files will not be purged by GC. If session data storage does not need to
561  * update timestamp, it should return SUCCESS simply. (e.g. Memcache)
562  * *mod_data, *key, *val are guaranteed to have non-NULL values.
563  *
564  * NOTE: Updating access timestamp at PS_READ_FUNC() may extend life of obsolete
565  * session data. Use of PS_UPDATE_TIMESTAMP_FUNC() is preferred whenever it is
566  * possible.
567  */
PS_UPDATE_TIMESTAMP_FUNC(files)568 PS_UPDATE_TIMESTAMP_FUNC(files)
569 {
570 	char buf[MAXPATHLEN];
571 	int ret;
572 	PS_FILES_DATA;
573 
574 	if (!ps_files_path_create(buf, sizeof(buf), data, ZSTR_VAL(key))) {
575 		return FAILURE;
576 	}
577 
578 	/* Update mtime */
579 	ret = VCWD_UTIME(buf, NULL);
580 	if (ret == -1) {
581 		/* New session ID, create data file */
582 		return ps_files_write(data, key, val);
583 	}
584 
585 	return SUCCESS;
586 }
587 
588 
589 /*
590  * Delete session data.
591  * PARAMETERS: PS_DESTROY_ARGS in php_session.h
592  * RETURN VALUE: SUCCESS or FAILURE.
593  *
594  * PS_DESTROY_FUNC() must remove the session data specified by *key from
595  * session data storage unconditionally. It must not return FAILURE for
596  * non-existent session data.
597  * *mod_data, *key are guaranteed to have non-NULL values.
598  */
PS_DESTROY_FUNC(files)599 PS_DESTROY_FUNC(files)
600 {
601 	char buf[MAXPATHLEN];
602 	PS_FILES_DATA;
603 
604 	if (!ps_files_path_create(buf, sizeof(buf), data, ZSTR_VAL(key))) {
605 		return FAILURE;
606 	}
607 
608 	if (data->fd != -1) {
609 		ps_files_close(data);
610 
611 		if (VCWD_UNLINK(buf) == -1) {
612 			/* This is a little safety check for instances when we are dealing with a regenerated session
613 			 * that was not yet written to disk. */
614 			if (!VCWD_ACCESS(buf, F_OK)) {
615 				return FAILURE;
616 			}
617 		}
618 	}
619 
620 	return SUCCESS;
621 }
622 
623 
624 /*
625  * Cleanup expired session data.
626  * PARAMETERS: PS_GC_ARGS in php_session.h
627  * RETURN VALUE: SUCCESS or FAILURE. Number of deleted records(int *nrdels(default=-1)).
628  *
629  * PS_GC_FUNC() must remove session data that are not accessed
630  * 'session.maxlifetime'(seconds). If storage does not need manual GC, it
631  * may return SUCCESS simply. (e.g. Memcache) It must set number of records
632  * deleted(nrdels).
633  * *mod_data is guaranteed to have non-NULL value.
634  */
PS_GC_FUNC(files)635 PS_GC_FUNC(files)
636 {
637 	PS_FILES_DATA;
638 
639 	/* We don't perform any cleanup, if dirdepth is larger than 0.
640 	   we return SUCCESS, since all cleanup should be handled by
641 	   an external entity (i.e. find -ctime x | xargs rm) */
642 
643 	if (data->dirdepth == 0) {
644 		*nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime);
645 	} else {
646 		*nrdels = -1; // Cannot process multiple depth save dir
647 	}
648 
649 	return *nrdels;
650 }
651 
652 
653 /*
654  * Create session ID.
655  * PARAMETERS: PS_CREATE_SID_ARGS in php_session.h
656  * RETURN VALUE: Valid session ID(zend_string *) or NULL for FAILURE.
657  *
658  * PS_CREATE_SID_FUNC() must check collision. i.e. Check session data if
659  * new sid exists already.
660  * *mod_data is guaranteed to have non-NULL value.
661  * NOTE: Default php_session_create_id() does not check collision. If
662  * NULL is returned, session module create new ID by using php_session_create_id().
663  * If php_session_create_id() fails due to invalid configuration, it raises E_ERROR.
664  * NULL return value checks from php_session_create_id() is not required generally.
665  */
PS_CREATE_SID_FUNC(files)666 PS_CREATE_SID_FUNC(files)
667 {
668 	zend_string *sid;
669 	int maxfail = 3;
670 	PS_FILES_DATA;
671 
672 	do {
673 		sid = php_session_create_id((void**)&data);
674 		if (!sid) {
675 			if (--maxfail < 0) {
676 				return NULL;
677 			} else {
678 				continue;
679 			}
680 		}
681 		/* Check collision */
682 		/* FIXME: mod_data(data) should not be NULL (User handler could be NULL) */
683 		if (data && ps_files_key_exists(data, ZSTR_VAL(sid)) == SUCCESS) {
684 			if (sid) {
685 				zend_string_release_ex(sid, 0);
686 				sid = NULL;
687 			}
688 			if (--maxfail < 0) {
689 				return NULL;
690 			}
691 		}
692 	} while(!sid);
693 
694 	return sid;
695 }
696 
697 
698 /*
699  * Check session ID existence for use_strict_mode support.
700  * PARAMETERS: PS_VALIDATE_SID_ARGS in php_session.h
701  * RETURN VALUE: SUCCESS or FAILURE.
702  *
703  * Return SUCCESS for valid key(already existing session).
704  * Return FAILURE for invalid key(non-existing session).
705  * *mod_data, *key are guaranteed to have non-NULL values.
706  */
PS_VALIDATE_SID_FUNC(files)707 PS_VALIDATE_SID_FUNC(files)
708 {
709 	PS_FILES_DATA;
710 
711 	return ps_files_key_exists(data, ZSTR_VAL(key));
712 }
713