Annotation of embedaddon/php/ext/session/mod_files.c, revision 1.1.1.4
1.1 misho 1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
1.1.1.4 ! misho 5: | Copyright (c) 1997-2014 The PHP Group |
1.1 misho 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:
1.1.1.2 misho 19: /* $Id$ */
1.1 misho 20:
21: #include "php.h"
22:
23: #include <sys/stat.h>
24: #include <sys/types.h>
25:
26: #if HAVE_SYS_FILE_H
27: #include <sys/file.h>
28: #endif
29:
30: #if HAVE_DIRENT_H
31: #include <dirent.h>
32: #endif
33:
34: #ifdef PHP_WIN32
35: #include "win32/readdir.h"
36: #endif
37: #include <time.h>
38:
39: #include <fcntl.h>
40: #include <errno.h>
41:
42: #if HAVE_UNISTD_H
43: #include <unistd.h>
44: #endif
45:
46: #include "php_session.h"
47: #include "mod_files.h"
48: #include "ext/standard/flock_compat.h"
49: #include "php_open_temporary_file.h"
50:
51: #define FILE_PREFIX "sess_"
52:
1.1.1.4 ! misho 53: #ifdef PHP_WIN32
! 54: # ifndef O_NOFOLLOW
! 55: # define O_NOFOLLOW 0
! 56: # endif
! 57: #endif
! 58:
1.1 misho 59: typedef struct {
60: int fd;
61: char *lastkey;
62: char *basedir;
63: size_t basedir_len;
64: size_t dirdepth;
65: size_t st_size;
66: int filemode;
67: } ps_files;
68:
69: ps_module ps_mod_files = {
70: PS_MOD(files)
71: };
72:
73: /* If you change the logic here, please also update the error message in
74: * ps_files_open() appropriately */
75: static int ps_files_valid_key(const char *key)
76: {
77: size_t len;
78: const char *p;
79: char c;
80: int ret = 1;
81:
82: for (p = key; (c = *p); p++) {
83: /* valid characters are a..z,A..Z,0..9 */
84: if (!((c >= 'a' && c <= 'z')
85: || (c >= 'A' && c <= 'Z')
86: || (c >= '0' && c <= '9')
87: || c == ','
88: || c == '-')) {
89: ret = 0;
90: break;
91: }
92: }
93:
94: len = p - key;
95:
96: /* Somewhat arbitrary length limit here, but should be way more than
97: anyone needs and avoids file-level warnings later on if we exceed MAX_PATH */
98: if (len == 0 || len > 128) {
99: ret = 0;
100: }
101:
102: return ret;
103: }
104:
105: static char *ps_files_path_create(char *buf, size_t buflen, ps_files *data, const char *key)
106: {
107: size_t key_len;
108: const char *p;
109: int i;
110: int n;
111:
112: key_len = strlen(key);
113: if (key_len <= data->dirdepth ||
114: buflen < (strlen(data->basedir) + 2 * data->dirdepth + key_len + 5 + sizeof(FILE_PREFIX))) {
115: return NULL;
116: }
117:
118: p = key;
119: memcpy(buf, data->basedir, data->basedir_len);
120: n = data->basedir_len;
121: buf[n++] = PHP_DIR_SEPARATOR;
122: for (i = 0; i < (int)data->dirdepth; i++) {
123: buf[n++] = *p++;
124: buf[n++] = PHP_DIR_SEPARATOR;
125: }
126: memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
127: n += sizeof(FILE_PREFIX) - 1;
128: memcpy(buf + n, key, key_len);
129: n += key_len;
130: buf[n] = '\0';
131:
132: return buf;
133: }
134:
135: #ifndef O_BINARY
136: # define O_BINARY 0
137: #endif
138:
139: static void ps_files_close(ps_files *data)
140: {
141: if (data->fd != -1) {
142: #ifdef PHP_WIN32
143: /* On Win32 locked files that are closed without being explicitly unlocked
144: will be unlocked only when "system resources become available". */
145: flock(data->fd, LOCK_UN);
146: #endif
147: close(data->fd);
148: data->fd = -1;
149: }
150: }
151:
152: static void ps_files_open(ps_files *data, const char *key TSRMLS_DC)
153: {
154: char buf[MAXPATHLEN];
1.1.1.4 ! misho 155: struct stat sbuf;
1.1 misho 156:
157: if (data->fd < 0 || !data->lastkey || strcmp(key, data->lastkey)) {
158: if (data->lastkey) {
159: efree(data->lastkey);
160: data->lastkey = NULL;
161: }
162:
163: ps_files_close(data);
164:
165: if (!ps_files_valid_key(key)) {
166: php_error_docref(NULL TSRMLS_CC, E_WARNING, "The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,'");
167: PS(invalid_session_id) = 1;
168: return;
169: }
170: if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
171: return;
172: }
173:
174: data->lastkey = estrdup(key);
175:
1.1.1.4 ! misho 176: /* O_NOFOLLOW to prevent us from following evil symlinks */
! 177: #ifdef O_NOFOLLOW
! 178: data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY | O_NOFOLLOW, data->filemode);
! 179: #else
! 180: /* Check to make sure that the opened file is not outside of allowable dirs.
! 181: This is not 100% safe but it's hard to do something better without O_NOFOLLOW */
! 182: if(PG(open_basedir) && lstat(buf, &sbuf) == 0 && S_ISLNK(sbuf.st_mode) && php_check_open_basedir(buf TSRMLS_CC)) {
! 183: return;
! 184: }
1.1 misho 185: data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY, data->filemode);
1.1.1.4 ! misho 186: #endif
1.1 misho 187:
188: if (data->fd != -1) {
189: #ifndef PHP_WIN32
1.1.1.4 ! misho 190: /* check that this session file was created by us or root – we
! 191: don't want to end up accepting the sessions of another webapp */
! 192: if (fstat(data->fd, &sbuf) || (sbuf.st_uid != 0 && sbuf.st_uid != getuid() && sbuf.st_uid != geteuid())) {
! 193: close(data->fd);
! 194: data->fd = -1;
! 195: return;
1.1 misho 196: }
197: #endif
198: flock(data->fd, LOCK_EX);
199:
200: #ifdef F_SETFD
201: # ifndef FD_CLOEXEC
202: # define FD_CLOEXEC 1
203: # endif
204: if (fcntl(data->fd, F_SETFD, FD_CLOEXEC)) {
205: php_error_docref(NULL TSRMLS_CC, E_WARNING, "fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)", data->fd, strerror(errno), errno);
206: }
207: #endif
208: } else {
209: php_error_docref(NULL TSRMLS_CC, E_WARNING, "open(%s, O_RDWR) failed: %s (%d)", buf, strerror(errno), errno);
210: }
211: }
212: }
213:
214: static int ps_files_cleanup_dir(const char *dirname, int maxlifetime TSRMLS_DC)
215: {
216: DIR *dir;
217: char dentry[sizeof(struct dirent) + MAXPATHLEN];
218: struct dirent *entry = (struct dirent *) &dentry;
219: struct stat sbuf;
220: char buf[MAXPATHLEN];
221: time_t now;
222: int nrdels = 0;
223: size_t dirname_len;
224:
225: dir = opendir(dirname);
226: if (!dir) {
227: php_error_docref(NULL TSRMLS_CC, E_NOTICE, "ps_files_cleanup_dir: opendir(%s) failed: %s (%d)", dirname, strerror(errno), errno);
228: return (0);
229: }
230:
231: time(&now);
232:
233: dirname_len = strlen(dirname);
234:
235: /* Prepare buffer (dirname never changes) */
236: memcpy(buf, dirname, dirname_len);
237: buf[dirname_len] = PHP_DIR_SEPARATOR;
238:
239: while (php_readdir_r(dir, (struct dirent *) dentry, &entry) == 0 && entry) {
240: /* does the file start with our prefix? */
241: if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
242: size_t entry_len = strlen(entry->d_name);
243:
244: /* does it fit into our buffer? */
245: if (entry_len + dirname_len + 2 < MAXPATHLEN) {
246: /* create the full path.. */
247: memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
248:
249: /* NUL terminate it and */
250: buf[dirname_len + entry_len + 1] = '\0';
251:
252: /* check whether its last access was more than maxlifet ago */
253: if (VCWD_STAT(buf, &sbuf) == 0 &&
254: (now - sbuf.st_mtime) > maxlifetime) {
255: VCWD_UNLINK(buf);
256: nrdels++;
257: }
258: }
259: }
260: }
261:
262: closedir(dir);
263:
264: return (nrdels);
265: }
266:
267: #define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()
268:
269: PS_OPEN_FUNC(files)
270: {
271: ps_files *data;
272: const char *p, *last;
273: const char *argv[3];
274: int argc = 0;
275: size_t dirdepth = 0;
276: int filemode = 0600;
277:
278: if (*save_path == '\0') {
279: /* if save path is an empty string, determine the temporary dir */
280: save_path = php_get_temporary_directory();
281:
282: if (php_check_open_basedir(save_path TSRMLS_CC)) {
283: return FAILURE;
284: }
285: }
286:
287: /* split up input parameter */
288: last = save_path;
289: p = strchr(save_path, ';');
290: while (p) {
291: argv[argc++] = last;
292: last = ++p;
293: p = strchr(p, ';');
294: if (argc > 1) break;
295: }
296: argv[argc++] = last;
297:
298: if (argc > 1) {
299: errno = 0;
300: dirdepth = (size_t) strtol(argv[0], NULL, 10);
301: if (errno == ERANGE) {
302: php_error(E_WARNING, "The first parameter in session.save_path is invalid");
303: return FAILURE;
304: }
305: }
306:
307: if (argc > 2) {
308: errno = 0;
309: filemode = strtol(argv[1], NULL, 8);
310: if (errno == ERANGE || filemode < 0 || filemode > 07777) {
311: php_error(E_WARNING, "The second parameter in session.save_path is invalid");
312: return FAILURE;
313: }
314: }
315: save_path = argv[argc - 1];
316:
317: data = ecalloc(1, sizeof(*data));
318:
319: data->fd = -1;
320: data->dirdepth = dirdepth;
321: data->filemode = filemode;
322: data->basedir_len = strlen(save_path);
323: data->basedir = estrndup(save_path, data->basedir_len);
324:
1.1.1.2 misho 325: if (PS_GET_MOD_DATA()) {
326: ps_close_files(mod_data TSRMLS_CC);
327: }
1.1 misho 328: PS_SET_MOD_DATA(data);
329:
330: return SUCCESS;
331: }
332:
333: PS_CLOSE_FUNC(files)
334: {
335: PS_FILES_DATA;
336:
337: ps_files_close(data);
338:
339: if (data->lastkey) {
340: efree(data->lastkey);
341: }
342:
343: efree(data->basedir);
344: efree(data);
345: *mod_data = NULL;
346:
347: return SUCCESS;
348: }
349:
350: PS_READ_FUNC(files)
351: {
352: long n;
353: struct stat sbuf;
354: PS_FILES_DATA;
355:
356: ps_files_open(data, key TSRMLS_CC);
357: if (data->fd < 0) {
358: return FAILURE;
359: }
360:
361: if (fstat(data->fd, &sbuf)) {
362: return FAILURE;
363: }
364:
365: data->st_size = *vallen = sbuf.st_size;
366:
367: if (sbuf.st_size == 0) {
368: *val = STR_EMPTY_ALLOC();
369: return SUCCESS;
370: }
371:
372: *val = emalloc(sbuf.st_size);
373:
374: #if defined(HAVE_PREAD)
375: n = pread(data->fd, *val, sbuf.st_size, 0);
376: #else
377: lseek(data->fd, 0, SEEK_SET);
378: n = read(data->fd, *val, sbuf.st_size);
379: #endif
380:
381: if (n != sbuf.st_size) {
382: if (n == -1) {
383: php_error_docref(NULL TSRMLS_CC, E_WARNING, "read failed: %s (%d)", strerror(errno), errno);
384: } else {
385: php_error_docref(NULL TSRMLS_CC, E_WARNING, "read returned less bytes than requested");
386: }
387: efree(*val);
388: return FAILURE;
389: }
390:
391: return SUCCESS;
392: }
393:
394: PS_WRITE_FUNC(files)
395: {
396: long n;
397: PS_FILES_DATA;
398:
399: ps_files_open(data, key TSRMLS_CC);
400: if (data->fd < 0) {
401: return FAILURE;
402: }
403:
404: /* Truncate file if the amount of new data is smaller than the existing data set. */
405:
406: if (vallen < (int)data->st_size) {
1.1.1.2 misho 407: php_ignore_value(ftruncate(data->fd, 0));
1.1 misho 408: }
409:
410: #if defined(HAVE_PWRITE)
411: n = pwrite(data->fd, val, vallen, 0);
412: #else
413: lseek(data->fd, 0, SEEK_SET);
414: n = write(data->fd, val, vallen);
415: #endif
416:
417: if (n != vallen) {
418: if (n == -1) {
419: php_error_docref(NULL TSRMLS_CC, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
420: } else {
421: php_error_docref(NULL TSRMLS_CC, E_WARNING, "write wrote less bytes than requested");
422: }
423: return FAILURE;
424: }
425:
426: return SUCCESS;
427: }
428:
429: PS_DESTROY_FUNC(files)
430: {
431: char buf[MAXPATHLEN];
432: PS_FILES_DATA;
433:
434: if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
435: return FAILURE;
436: }
437:
438: if (data->fd != -1) {
439: ps_files_close(data);
440:
441: if (VCWD_UNLINK(buf) == -1) {
442: /* This is a little safety check for instances when we are dealing with a regenerated session
443: * that was not yet written to disk. */
444: if (!VCWD_ACCESS(buf, F_OK)) {
445: return FAILURE;
446: }
447: }
448: }
449:
450: return SUCCESS;
451: }
452:
453: PS_GC_FUNC(files)
454: {
455: PS_FILES_DATA;
456:
457: /* we don't perform any cleanup, if dirdepth is larger than 0.
458: we return SUCCESS, since all cleanup should be handled by
459: an external entity (i.e. find -ctime x | xargs rm) */
460:
461: if (data->dirdepth == 0) {
462: *nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime TSRMLS_CC);
463: }
464:
465: return SUCCESS;
466: }
467:
468: /*
469: * Local variables:
470: * tab-width: 4
471: * c-basic-offset: 4
472: * End:
473: * vim600: sw=4 ts=4 fdm=marker
474: * vim<600: sw=4 ts=4
475: */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>