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

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