1 : /*
2 : +----------------------------------------------------------------------+
3 : | PHP Version 6 |
4 : +----------------------------------------------------------------------+
5 : | Copyright (c) 1997-2009 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 290190 2009-11-03 21:21:34Z guenter $ */
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 219 : {
71 : size_t len;
72 : const char *p;
73 : char c;
74 219 : int ret = 1;
75 :
76 6612 : for (p = key; (c = *p); p++) {
77 : /* valid characters are a..z,A..Z,0..9 */
78 6394 : if (!((c >= 'a' && c <= 'z')
79 : || (c >= 'A' && c <= 'Z')
80 : || (c >= '0' && c <= '9')
81 : || c == ','
82 : || c == '-')) {
83 1 : ret = 0;
84 1 : break;
85 : }
86 : }
87 :
88 219 : len = p - key;
89 :
90 219 : if (len == 0) {
91 1 : ret = 0;
92 : }
93 :
94 219 : return ret;
95 : }
96 :
97 : static char *ps_files_path_create(char *buf, size_t buflen, ps_files *data, const char *key TSRMLS_DC)
98 388 : {
99 : size_t key_len;
100 : const char *p;
101 : int i;
102 : int n;
103 :
104 388 : key_len = strlen(key);
105 388 : if (key_len <= data->dirdepth ||
106 : buflen < (strlen(data->basedir) + 2 * data->dirdepth + key_len + 5 + sizeof(FILE_PREFIX))) {
107 2 : return NULL;
108 : }
109 :
110 386 : p = key;
111 386 : memcpy(buf, data->basedir, data->basedir_len);
112 386 : n = data->basedir_len;
113 386 : buf[n++] = PHP_DIR_SEPARATOR;
114 386 : for (i = 0; i < (int)data->dirdepth; i++) {
115 0 : buf[n++] = *p++;
116 0 : buf[n++] = PHP_DIR_SEPARATOR;
117 : }
118 386 : memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
119 386 : n += sizeof(FILE_PREFIX) - 1;
120 386 : memcpy(buf + n, key, key_len);
121 386 : n += key_len;
122 386 : buf[n] = '\0';
123 :
124 386 : if (UG(filesystem_encoding_conv) &&
125 : ucnv_getType(UG(filesystem_encoding_conv)) != UCNV_UTF8) {
126 0 : char *newbuf = NULL;
127 : int newlen;
128 0 : UErrorCode status = U_ZERO_ERROR;
129 :
130 0 : zend_convert_encodings(ZEND_U_CONVERTER(UG(filesystem_encoding_conv)), UG(utf8_conv), &newbuf, &newlen, buf, n, &status);
131 :
132 0 : if (status != U_ZERO_ERROR) {
133 0 : php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Failure converting savepath to local filesystem encoding, attempting to use utf8");
134 : } else {
135 0 : if ((unsigned int)newlen >= buflen) {
136 0 : newlen = buflen - 1;
137 0 : newbuf[newlen] = 0;
138 : }
139 0 : memcpy(buf, newbuf, newlen + 1);
140 : }
141 0 : if (newbuf) {
142 0 : efree(newbuf);
143 : }
144 : }
145 :
146 386 : return buf;
147 : }
148 :
149 : #ifndef O_BINARY
150 : # define O_BINARY 0
151 : #endif
152 :
153 : static void ps_files_close(ps_files *data)
154 602 : {
155 602 : if (data->fd != -1) {
156 : #ifdef PHP_WIN32
157 : /* On Win32 locked files that are closed without being explicitly unlocked
158 : will be unlocked only when "system resources become available". */
159 : flock(data->fd, LOCK_UN);
160 : #endif
161 214 : close(data->fd);
162 214 : data->fd = -1;
163 : }
164 602 : }
165 :
166 : static void ps_files_open(ps_files *data, const char *key TSRMLS_DC)
167 265 : {
168 : char buf[MAXPATHLEN];
169 :
170 265 : if (data->fd < 0 || !data->lastkey || strcmp(key, data->lastkey)) {
171 219 : if (data->lastkey) {
172 1 : efree(data->lastkey);
173 1 : data->lastkey = NULL;
174 : }
175 :
176 219 : ps_files_close(data);
177 :
178 219 : if (!ps_files_valid_key(key)) {
179 1 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "The session id contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,'");
180 1 : PS(invalid_session_id) = 1;
181 1 : return;
182 : }
183 218 : if (!ps_files_path_create(buf, sizeof(buf), data, key TSRMLS_CC)) {
184 2 : return;
185 : }
186 :
187 216 : data->lastkey = estrdup(key);
188 :
189 216 : data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY, data->filemode);
190 :
191 216 : if (data->fd != -1) {
192 : #ifndef PHP_WIN32
193 : /* check to make sure that the opened file is not a symlink, linking to data outside of allowable dirs */
194 214 : if (PG(open_basedir)) {
195 : struct stat sbuf;
196 :
197 214 : if (fstat(data->fd, &sbuf)) {
198 0 : close(data->fd);
199 0 : return;
200 : }
201 214 : if (
202 : S_ISLNK(sbuf.st_mode) &&
203 : php_check_open_basedir(buf TSRMLS_CC)
204 : ) {
205 0 : close(data->fd);
206 0 : return;
207 : }
208 : }
209 : #endif
210 214 : flock(data->fd, LOCK_EX);
211 :
212 : #ifdef F_SETFD
213 : # ifndef FD_CLOEXEC
214 : # define FD_CLOEXEC 1
215 : # endif
216 214 : if (fcntl(data->fd, F_SETFD, FD_CLOEXEC)) {
217 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "fcntl(%d, F_SETFD, FD_CLOEXEC) failed: %s (%d)", data->fd, strerror(errno), errno);
218 : }
219 : #endif
220 : } else {
221 2 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "open(%s, O_RDWR) failed: %s (%d)", buf, strerror(errno), errno);
222 : }
223 : }
224 : }
225 :
226 : static int ps_files_cleanup_dir(const char *dirname, int maxlifetime TSRMLS_DC)
227 4 : {
228 : DIR *dir;
229 : char dentry[sizeof(struct dirent) + MAXPATHLEN];
230 4 : struct dirent *entry = (struct dirent *) &dentry;
231 : struct stat sbuf;
232 : char buf[MAXPATHLEN];
233 : time_t now;
234 4 : int nrdels = 0;
235 : size_t dirname_len;
236 :
237 4 : dir = opendir(dirname);
238 4 : if (!dir) {
239 0 : php_error_docref(NULL TSRMLS_CC, E_NOTICE, "ps_files_cleanup_dir: opendir(%s) failed: %s (%d)", dirname, strerror(errno), errno);
240 0 : return (0);
241 : }
242 :
243 4 : time(&now);
244 :
245 4 : dirname_len = strlen(dirname);
246 :
247 : /* Prepare buffer (dirname never changes) */
248 4 : memcpy(buf, dirname, dirname_len);
249 4 : buf[dirname_len] = PHP_DIR_SEPARATOR;
250 :
251 1266 : while (php_readdir_r(dir, (struct dirent *) dentry, &entry) == 0 && entry) {
252 : /* does the file start with our prefix? */
253 1258 : if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
254 42 : size_t entry_len = strlen(entry->d_name);
255 :
256 : /* does it fit into our buffer? */
257 42 : if (entry_len + dirname_len + 2 < MAXPATHLEN) {
258 : /* create the full path.. */
259 42 : memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
260 :
261 : /* NUL terminate it and */
262 42 : buf[dirname_len + entry_len + 1] = '\0';
263 :
264 : /* check whether its last access was more than maxlifet ago */
265 42 : if (VCWD_STAT(buf, &sbuf) == 0 &&
266 : (now - sbuf.st_mtime) > maxlifetime) {
267 29 : VCWD_UNLINK(buf);
268 29 : nrdels++;
269 : }
270 : }
271 : }
272 : }
273 :
274 4 : closedir(dir);
275 :
276 4 : return (nrdels);
277 : }
278 :
279 : #define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA()
280 :
281 : PS_OPEN_FUNC(files)
282 216 : {
283 : ps_files *data;
284 : const char *p, *last;
285 : const char *argv[3];
286 216 : int argc = 0;
287 216 : size_t dirdepth = 0;
288 216 : int filemode = 0600;
289 :
290 216 : if (*save_path == '\0') {
291 : /* if save path is an empty string, determine the temporary dir */
292 205 : save_path = php_get_temporary_directory();
293 :
294 205 : if (php_check_open_basedir(save_path TSRMLS_CC)) {
295 0 : return FAILURE;
296 : }
297 : }
298 :
299 : /* split up input parameter */
300 216 : last = save_path;
301 216 : p = strchr(save_path, ';');
302 434 : while (p) {
303 4 : argv[argc++] = last;
304 4 : last = ++p;
305 4 : p = strchr(p, ';');
306 4 : if (argc > 1) break;
307 : }
308 216 : argv[argc++] = last;
309 :
310 216 : if (argc > 1) {
311 2 : errno = 0;
312 2 : dirdepth = (size_t) strtol(argv[0], NULL, 10);
313 2 : if (errno == ERANGE) {
314 0 : php_error(E_WARNING, "The first parameter in session.save_path is invalid");
315 0 : return FAILURE;
316 : }
317 : }
318 :
319 216 : if (argc > 2) {
320 2 : errno = 0;
321 2 : filemode = strtol(argv[1], NULL, 8);
322 2 : if (errno == ERANGE || filemode < 0 || filemode > 07777) {
323 0 : php_error(E_WARNING, "The second parameter in session.save_path is invalid");
324 0 : return FAILURE;
325 : }
326 : }
327 216 : save_path = argv[argc - 1];
328 :
329 216 : data = ecalloc(1, sizeof(*data));
330 :
331 216 : data->fd = -1;
332 216 : data->dirdepth = dirdepth;
333 216 : data->filemode = filemode;
334 216 : data->basedir_len = strlen(save_path);
335 216 : data->basedir = estrndup(save_path, data->basedir_len);
336 :
337 216 : PS_SET_MOD_DATA(data);
338 :
339 216 : return SUCCESS;
340 : }
341 :
342 : PS_CLOSE_FUNC(files)
343 216 : {
344 216 : PS_FILES_DATA;
345 :
346 216 : ps_files_close(data);
347 :
348 216 : if (data->lastkey) {
349 215 : efree(data->lastkey);
350 : }
351 :
352 216 : efree(data->basedir);
353 216 : efree(data);
354 216 : *mod_data = NULL;
355 :
356 216 : return SUCCESS;
357 : }
358 :
359 : PS_READ_FUNC(files)
360 217 : {
361 : long n;
362 : struct stat sbuf;
363 217 : PS_FILES_DATA;
364 :
365 217 : ps_files_open(data, key TSRMLS_CC);
366 217 : if (data->fd < 0) {
367 4 : return FAILURE;
368 : }
369 :
370 213 : if (fstat(data->fd, &sbuf)) {
371 0 : return FAILURE;
372 : }
373 :
374 213 : data->st_size = *vallen = sbuf.st_size;
375 :
376 213 : if (sbuf.st_size == 0) {
377 207 : *val = STR_EMPTY_ALLOC();
378 207 : return SUCCESS;
379 : }
380 :
381 6 : *val = emalloc(sbuf.st_size);
382 :
383 : #if defined(HAVE_PREAD)
384 6 : n = pread(data->fd, *val, sbuf.st_size, 0);
385 : #else
386 : lseek(data->fd, 0, SEEK_SET);
387 : n = read(data->fd, *val, sbuf.st_size);
388 : #endif
389 :
390 6 : if (n != sbuf.st_size) {
391 0 : if (n == -1) {
392 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "read failed: %s (%d)", strerror(errno), errno);
393 : } else {
394 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "read returned less bytes than requested");
395 : }
396 0 : efree(*val);
397 0 : return FAILURE;
398 : }
399 :
400 6 : return SUCCESS;
401 : }
402 :
403 : PS_WRITE_FUNC(files)
404 48 : {
405 : long n;
406 48 : PS_FILES_DATA;
407 :
408 48 : ps_files_open(data, key TSRMLS_CC);
409 48 : if (data->fd < 0) {
410 1 : return FAILURE;
411 : }
412 :
413 : /* Truncate file if the amount of new data is smaller than the existing data set. */
414 :
415 47 : if (vallen < (int)data->st_size) {
416 1 : ftruncate(data->fd, 0);
417 : }
418 :
419 : #if defined(HAVE_PWRITE)
420 47 : n = pwrite(data->fd, val, vallen, 0);
421 : #else
422 : lseek(data->fd, 0, SEEK_SET);
423 : n = write(data->fd, val, vallen);
424 : #endif
425 :
426 47 : if (n != vallen) {
427 0 : if (n == -1) {
428 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
429 : } else {
430 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "write wrote less bytes than requested");
431 : }
432 0 : return FAILURE;
433 : }
434 :
435 47 : return SUCCESS;
436 : }
437 :
438 : PS_DESTROY_FUNC(files)
439 170 : {
440 : char buf[MAXPATHLEN];
441 170 : PS_FILES_DATA;
442 :
443 170 : if (!ps_files_path_create(buf, sizeof(buf), data, key TSRMLS_CC)) {
444 0 : return FAILURE;
445 : }
446 :
447 170 : if (data->fd != -1) {
448 167 : ps_files_close(data);
449 :
450 167 : if (VCWD_UNLINK(buf) == -1) {
451 : /* This is a little safety check for instances when we are dealing with a regenerated session
452 : * that was not yet written to disk. */
453 6 : if (!VCWD_ACCESS(buf, F_OK)) {
454 0 : return FAILURE;
455 : }
456 : }
457 : }
458 :
459 170 : return SUCCESS;
460 : }
461 :
462 : PS_GC_FUNC(files)
463 4 : {
464 4 : PS_FILES_DATA;
465 :
466 : /* we don't perform any cleanup, if dirdepth is larger than 0.
467 : we return SUCCESS, since all cleanup should be handled by
468 : an external entity (i.e. find -ctime x | xargs rm) */
469 :
470 4 : if (data->dirdepth == 0) {
471 4 : *nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime TSRMLS_CC);
472 : }
473 :
474 4 : return SUCCESS;
475 : }
476 :
477 : /*
478 : * Local variables:
479 : * tab-width: 4
480 : * c-basic-offset: 4
481 : * End:
482 : * vim600: sw=4 ts=4 fdm=marker
483 : * vim<600: sw=4 ts=4
484 : */
|