Annotation of embedaddon/php/ext/session/mod_files.c, revision 1.1.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>