Annotation of embedaddon/php/ext/session/mod_files.c, revision 1.1

1.1     ! misho       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 321634 2012-01-01 13:15:04Z felipe $ */
        !            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>