File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / php / ext / session / mod_files.c
Revision 1.1.1.4 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Sun Jun 15 20:03:55 2014 UTC (10 years ago) by misho
Branches: php, MAIN
CVS tags: v5_4_29, HEAD
php 5.4.29

    1: /*
    2:    +----------------------------------------------------------------------+
    3:    | PHP Version 5                                                        |
    4:    +----------------------------------------------------------------------+
    5:    | Copyright (c) 1997-2014 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.4 2014/06/15 20:03:55 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: #ifdef PHP_WIN32
   54: # ifndef O_NOFOLLOW
   55: #  define O_NOFOLLOW 0
   56: # endif
   57: #endif
   58: 
   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];
  155:     struct stat sbuf;
  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: 
  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: 		}
  185: 		data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY, data->filemode);
  186: #endif
  187: 
  188: 		if (data->fd != -1) {
  189: #ifndef PHP_WIN32
  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;
  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: 
  325: 	if (PS_GET_MOD_DATA()) {
  326: 		ps_close_files(mod_data TSRMLS_CC);
  327: 	}
  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) {
  407: 		php_ignore_value(ftruncate(data->fd, 0));
  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>