File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / php / ext / session / mod_files.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:48:01 2012 UTC (12 years, 4 months ago) by misho
Branches: php, MAIN
CVS tags: v5_3_10, HEAD
php

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>