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

1.1       misho       1: /*
                      2:    +----------------------------------------------------------------------+
                      3:    | PHP Version 5                                                        |
                      4:    +----------------------------------------------------------------------+
1.1.1.3 ! misho       5:    | Copyright (c) 1997-2013 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: 
                     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 */
1.1.1.2   misho     174:                        if (PG(open_basedir)) {
1.1       misho     175:                                struct stat sbuf;
                    176: 
                    177:                                if (fstat(data->fd, &sbuf)) {
                    178:                                        close(data->fd);
                    179:                                        return;
                    180:                                }
1.1.1.2   misho     181:                                if (S_ISLNK(sbuf.st_mode) && php_check_open_basedir(buf TSRMLS_CC)) {
1.1       misho     182:                                        close(data->fd);
                    183:                                        return;
                    184:                                }
                    185:                        }
                    186: #endif
                    187:                        flock(data->fd, LOCK_EX);
                    188: 
                    189: #ifdef F_SETFD
                    190: # ifndef FD_CLOEXEC
                    191: #  define FD_CLOEXEC 1
                    192: # endif
                    193:                        if (fcntl(data->fd, F_SETFD, FD_CLOEXEC)) {
                    194:                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)", data->fd, strerror(errno), errno);
                    195:                        }
                    196: #endif
                    197:                } else {
                    198:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "open(%s, O_RDWR) failed: %s (%d)", buf, strerror(errno), errno);
                    199:                }
                    200:        }
                    201: }
                    202: 
                    203: static int ps_files_cleanup_dir(const char *dirname, int maxlifetime TSRMLS_DC)
                    204: {
                    205:        DIR *dir;
                    206:        char dentry[sizeof(struct dirent) + MAXPATHLEN];
                    207:        struct dirent *entry = (struct dirent *) &dentry;
                    208:        struct stat sbuf;
                    209:        char buf[MAXPATHLEN];
                    210:        time_t now;
                    211:        int nrdels = 0;
                    212:        size_t dirname_len;
                    213: 
                    214:        dir = opendir(dirname);
                    215:        if (!dir) {
                    216:                php_error_docref(NULL TSRMLS_CC, E_NOTICE, "ps_files_cleanup_dir: opendir(%s) failed: %s (%d)", dirname, strerror(errno), errno);
                    217:                return (0);
                    218:        }
                    219: 
                    220:        time(&now);
                    221: 
                    222:        dirname_len = strlen(dirname);
                    223: 
                    224:        /* Prepare buffer (dirname never changes) */
                    225:        memcpy(buf, dirname, dirname_len);
                    226:        buf[dirname_len] = PHP_DIR_SEPARATOR;
                    227: 
                    228:        while (php_readdir_r(dir, (struct dirent *) dentry, &entry) == 0 && entry) {
                    229:                /* does the file start with our prefix? */
                    230:                if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
                    231:                        size_t entry_len = strlen(entry->d_name);
                    232: 
                    233:                        /* does it fit into our buffer? */
                    234:                        if (entry_len + dirname_len + 2 < MAXPATHLEN) {
                    235:                                /* create the full path.. */
                    236:                                memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
                    237: 
                    238:                                /* NUL terminate it and */
                    239:                                buf[dirname_len + entry_len + 1] = '\0';
                    240: 
                    241:                                /* check whether its last access was more than maxlifet ago */
                    242:                                if (VCWD_STAT(buf, &sbuf) == 0 &&
                    243:                                                (now - sbuf.st_mtime) > maxlifetime) {
                    244:                                        VCWD_UNLINK(buf);
                    245:                                        nrdels++;
                    246:                                }
                    247:                        }
                    248:                }
                    249:        }
                    250: 
                    251:        closedir(dir);
                    252: 
                    253:        return (nrdels);
                    254: }
                    255: 
                    256: #define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()
                    257: 
                    258: PS_OPEN_FUNC(files)
                    259: {
                    260:        ps_files *data;
                    261:        const char *p, *last;
                    262:        const char *argv[3];
                    263:        int argc = 0;
                    264:        size_t dirdepth = 0;
                    265:        int filemode = 0600;
                    266: 
                    267:        if (*save_path == '\0') {
                    268:                /* if save path is an empty string, determine the temporary dir */
                    269:                save_path = php_get_temporary_directory();
                    270: 
                    271:                if (php_check_open_basedir(save_path TSRMLS_CC)) {
                    272:                        return FAILURE;
                    273:                }
                    274:        }
                    275: 
                    276:        /* split up input parameter */
                    277:        last = save_path;
                    278:        p = strchr(save_path, ';');
                    279:        while (p) {
                    280:                argv[argc++] = last;
                    281:                last = ++p;
                    282:                p = strchr(p, ';');
                    283:                if (argc > 1) break;
                    284:        }
                    285:        argv[argc++] = last;
                    286: 
                    287:        if (argc > 1) {
                    288:                errno = 0;
                    289:                dirdepth = (size_t) strtol(argv[0], NULL, 10);
                    290:                if (errno == ERANGE) {
                    291:                        php_error(E_WARNING, "The first parameter in session.save_path is invalid");
                    292:                        return FAILURE;
                    293:                }
                    294:        }
                    295: 
                    296:        if (argc > 2) {
                    297:                errno = 0;
                    298:                filemode = strtol(argv[1], NULL, 8);
                    299:                if (errno == ERANGE || filemode < 0 || filemode > 07777) {
                    300:                        php_error(E_WARNING, "The second parameter in session.save_path is invalid");
                    301:                        return FAILURE;
                    302:                }
                    303:        }
                    304:        save_path = argv[argc - 1];
                    305: 
                    306:        data = ecalloc(1, sizeof(*data));
                    307: 
                    308:        data->fd = -1;
                    309:        data->dirdepth = dirdepth;
                    310:        data->filemode = filemode;
                    311:        data->basedir_len = strlen(save_path);
                    312:        data->basedir = estrndup(save_path, data->basedir_len);
                    313: 
1.1.1.2   misho     314:        if (PS_GET_MOD_DATA()) {
                    315:                ps_close_files(mod_data TSRMLS_CC);
                    316:        }
1.1       misho     317:        PS_SET_MOD_DATA(data);
                    318: 
                    319:        return SUCCESS;
                    320: }
                    321: 
                    322: PS_CLOSE_FUNC(files)
                    323: {
                    324:        PS_FILES_DATA;
                    325: 
                    326:        ps_files_close(data);
                    327: 
                    328:        if (data->lastkey) {
                    329:                efree(data->lastkey);
                    330:        }
                    331: 
                    332:        efree(data->basedir);
                    333:        efree(data);
                    334:        *mod_data = NULL;
                    335: 
                    336:        return SUCCESS;
                    337: }
                    338: 
                    339: PS_READ_FUNC(files)
                    340: {
                    341:        long n;
                    342:        struct stat sbuf;
                    343:        PS_FILES_DATA;
                    344: 
                    345:        ps_files_open(data, key TSRMLS_CC);
                    346:        if (data->fd < 0) {
                    347:                return FAILURE;
                    348:        }
                    349: 
                    350:        if (fstat(data->fd, &sbuf)) {
                    351:                return FAILURE;
                    352:        }
                    353: 
                    354:        data->st_size = *vallen = sbuf.st_size;
                    355: 
                    356:        if (sbuf.st_size == 0) {
                    357:                *val = STR_EMPTY_ALLOC();
                    358:                return SUCCESS;
                    359:        }
                    360: 
                    361:        *val = emalloc(sbuf.st_size);
                    362: 
                    363: #if defined(HAVE_PREAD)
                    364:        n = pread(data->fd, *val, sbuf.st_size, 0);
                    365: #else
                    366:        lseek(data->fd, 0, SEEK_SET);
                    367:        n = read(data->fd, *val, sbuf.st_size);
                    368: #endif
                    369: 
                    370:        if (n != sbuf.st_size) {
                    371:                if (n == -1) {
                    372:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "read failed: %s (%d)", strerror(errno), errno);
                    373:                } else {
                    374:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "read returned less bytes than requested");
                    375:                }
                    376:                efree(*val);
                    377:                return FAILURE;
                    378:        }
                    379: 
                    380:        return SUCCESS;
                    381: }
                    382: 
                    383: PS_WRITE_FUNC(files)
                    384: {
                    385:        long n;
                    386:        PS_FILES_DATA;
                    387: 
                    388:        ps_files_open(data, key TSRMLS_CC);
                    389:        if (data->fd < 0) {
                    390:                return FAILURE;
                    391:        }
                    392: 
                    393:        /* Truncate file if the amount of new data is smaller than the existing data set. */
                    394: 
                    395:        if (vallen < (int)data->st_size) {
1.1.1.2   misho     396:                php_ignore_value(ftruncate(data->fd, 0));
1.1       misho     397:        }
                    398: 
                    399: #if defined(HAVE_PWRITE)
                    400:        n = pwrite(data->fd, val, vallen, 0);
                    401: #else
                    402:        lseek(data->fd, 0, SEEK_SET);
                    403:        n = write(data->fd, val, vallen);
                    404: #endif
                    405: 
                    406:        if (n != vallen) {
                    407:                if (n == -1) {
                    408:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
                    409:                } else {
                    410:                        php_error_docref(NULL TSRMLS_CC, E_WARNING, "write wrote less bytes than requested");
                    411:                }
                    412:                return FAILURE;
                    413:        }
                    414: 
                    415:        return SUCCESS;
                    416: }
                    417: 
                    418: PS_DESTROY_FUNC(files)
                    419: {
                    420:        char buf[MAXPATHLEN];
                    421:        PS_FILES_DATA;
                    422: 
                    423:        if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
                    424:                return FAILURE;
                    425:        }
                    426: 
                    427:        if (data->fd != -1) {
                    428:                ps_files_close(data);
                    429: 
                    430:                if (VCWD_UNLINK(buf) == -1) {
                    431:                        /* This is a little safety check for instances when we are dealing with a regenerated session
                    432:                         * that was not yet written to disk. */
                    433:                        if (!VCWD_ACCESS(buf, F_OK)) {
                    434:                                return FAILURE;
                    435:                        }
                    436:                }
                    437:        }
                    438: 
                    439:        return SUCCESS;
                    440: }
                    441: 
                    442: PS_GC_FUNC(files)
                    443: {
                    444:        PS_FILES_DATA;
                    445: 
                    446:        /* we don't perform any cleanup, if dirdepth is larger than 0.
                    447:           we return SUCCESS, since all cleanup should be handled by
                    448:           an external entity (i.e. find -ctime x | xargs rm) */
                    449: 
                    450:        if (data->dirdepth == 0) {
                    451:                *nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime TSRMLS_CC);
                    452:        }
                    453: 
                    454:        return SUCCESS;
                    455: }
                    456: 
                    457: /*
                    458:  * Local variables:
                    459:  * tab-width: 4
                    460:  * c-basic-offset: 4
                    461:  * End:
                    462:  * vim600: sw=4 ts=4 fdm=marker
                    463:  * vim<600: sw=4 ts=4
                    464:  */

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