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