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 290174 2009-11-03 17:16:45Z 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 290 : {
71 : size_t len;
72 : const char *p;
73 : char c;
74 290 : int ret = 1;
75 :
76 8406 : for (p = key; (c = *p); p++) {
77 : /* valid characters are a..z,A..Z,0..9 */
78 8116 : 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 290 : len = p - key;
89 :
90 290 : if (len == 0) {
91 0 : ret = 0;
92 : }
93 :
94 290 : return ret;
95 : }
96 :
97 : static char *ps_files_path_create(char *buf, size_t buflen, ps_files *data, const char *key)
98 522 : {
99 : size_t key_len;
100 : const char *p;
101 : int i;
102 : int n;
103 :
104 522 : key_len = strlen(key);
105 522 : 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 519 : p = key;
111 519 : memcpy(buf, data->basedir, data->basedir_len);
112 519 : n = data->basedir_len;
113 519 : buf[n++] = PHP_DIR_SEPARATOR;
114 519 : for (i = 0; i < (int)data->dirdepth; i++) {
115 0 : buf[n++] = *p++;
116 0 : buf[n++] = PHP_DIR_SEPARATOR;
117 : }
118 519 : memcpy(buf + n, FILE_PREFIX, sizeof(FILE_PREFIX) - 1);
119 519 : n += sizeof(FILE_PREFIX) - 1;
120 519 : memcpy(buf + n, key, key_len);
121 519 : n += key_len;
122 519 : buf[n] = '\0';
123 :
124 519 : 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 808 : {
133 808 : 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 286 : close(data->fd);
140 286 : data->fd = -1;
141 : }
142 808 : }
143 :
144 : static void ps_files_open(ps_files *data, const char *key TSRMLS_DC)
145 346 : {
146 : char buf[MAXPATHLEN];
147 :
148 346 : if (data->fd < 0 || !data->lastkey || strcmp(key, data->lastkey)) {
149 290 : if (data->lastkey) {
150 0 : efree(data->lastkey);
151 0 : data->lastkey = NULL;
152 : }
153 :
154 290 : ps_files_close(data);
155 :
156 290 : 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 290 : if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
162 2 : return;
163 : }
164 :
165 288 : data->lastkey = estrdup(key);
166 :
167 288 : data->fd = VCWD_OPEN_MODE(buf, O_CREAT | O_RDWR | O_BINARY, data->filemode);
168 :
169 288 : 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 286 : if (PG(safe_mode) || PG(open_basedir)) {
173 : struct stat sbuf;
174 :
175 286 : if (fstat(data->fd, &sbuf)) {
176 0 : close(data->fd);
177 0 : return;
178 : }
179 286 : 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 286 : flock(data->fd, LOCK_EX);
192 :
193 : #ifdef F_SETFD
194 : # ifndef FD_CLOEXEC
195 : # define FD_CLOEXEC 1
196 : # endif
197 286 : 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 4 : {
209 : DIR *dir;
210 : char dentry[sizeof(struct dirent) + MAXPATHLEN];
211 4 : struct dirent *entry = (struct dirent *) &dentry;
212 : struct stat sbuf;
213 : char buf[MAXPATHLEN];
214 : time_t now;
215 4 : int nrdels = 0;
216 : size_t dirname_len;
217 :
218 4 : dir = opendir(dirname);
219 4 : 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 4 : time(&now);
225 :
226 4 : dirname_len = strlen(dirname);
227 :
228 : /* Prepare buffer (dirname never changes) */
229 4 : memcpy(buf, dirname, dirname_len);
230 4 : buf[dirname_len] = PHP_DIR_SEPARATOR;
231 :
232 1144 : while (php_readdir_r(dir, (struct dirent *) dentry, &entry) == 0 && entry) {
233 : /* does the file start with our prefix? */
234 1136 : if (!strncmp(entry->d_name, FILE_PREFIX, sizeof(FILE_PREFIX) - 1)) {
235 38 : size_t entry_len = strlen(entry->d_name);
236 :
237 : /* does it fit into our buffer? */
238 38 : if (entry_len + dirname_len + 2 < MAXPATHLEN) {
239 : /* create the full path.. */
240 38 : memcpy(buf + dirname_len + 1, entry->d_name, entry_len);
241 :
242 : /* NUL terminate it and */
243 38 : buf[dirname_len + entry_len + 1] = '\0';
244 :
245 : /* check whether its last access was more than maxlifet ago */
246 38 : if (VCWD_STAT(buf, &sbuf) == 0 &&
247 : (now - sbuf.st_mtime) > maxlifetime) {
248 20 : VCWD_UNLINK(buf);
249 20 : nrdels++;
250 : }
251 : }
252 : }
253 : }
254 :
255 4 : closedir(dir);
256 :
257 4 : 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 (strcmp(save_path, "/tmp")) {
276 0 : if (PG(safe_mode) && (!php_checkuid(save_path, NULL, CHECKUID_CHECK_FILE_AND_DIR))) {
277 0 : return FAILURE;
278 : }
279 0 : if (php_check_open_basedir(save_path TSRMLS_CC)) {
280 0 : return FAILURE;
281 : }
282 : }
283 : }
284 :
285 : /* split up input parameter */
286 290 : last = save_path;
287 290 : p = strchr(save_path, ';');
288 582 : while (p) {
289 4 : argv[argc++] = last;
290 4 : last = ++p;
291 4 : p = strchr(p, ';');
292 4 : if (argc > 1) break;
293 : }
294 290 : argv[argc++] = last;
295 :
296 290 : if (argc > 1) {
297 2 : errno = 0;
298 2 : dirdepth = (size_t) strtol(argv[0], NULL, 10);
299 2 : if (errno == ERANGE) {
300 0 : php_error(E_WARNING, "The first parameter in session.save_path is invalid");
301 0 : return FAILURE;
302 : }
303 : }
304 :
305 290 : if (argc > 2) {
306 2 : errno = 0;
307 2 : filemode = strtol(argv[1], NULL, 8);
308 2 : if (errno == ERANGE || filemode < 0 || filemode > 07777) {
309 0 : php_error(E_WARNING, "The second parameter in session.save_path is invalid");
310 0 : return FAILURE;
311 : }
312 : }
313 290 : save_path = argv[argc - 1];
314 :
315 290 : data = ecalloc(1, sizeof(*data));
316 :
317 290 : data->fd = -1;
318 290 : data->dirdepth = dirdepth;
319 290 : data->filemode = filemode;
320 290 : data->basedir_len = strlen(save_path);
321 290 : data->basedir = estrndup(save_path, data->basedir_len);
322 :
323 290 : PS_SET_MOD_DATA(data);
324 :
325 290 : return SUCCESS;
326 : }
327 :
328 : PS_CLOSE_FUNC(files)
329 290 : {
330 290 : PS_FILES_DATA;
331 :
332 290 : ps_files_close(data);
333 :
334 290 : if (data->lastkey) {
335 288 : efree(data->lastkey);
336 : }
337 :
338 290 : efree(data->basedir);
339 290 : efree(data);
340 290 : *mod_data = NULL;
341 :
342 290 : return SUCCESS;
343 : }
344 :
345 : PS_READ_FUNC(files)
346 289 : {
347 : long n;
348 : struct stat sbuf;
349 289 : PS_FILES_DATA;
350 :
351 289 : ps_files_open(data, key TSRMLS_CC);
352 289 : if (data->fd < 0) {
353 3 : return FAILURE;
354 : }
355 :
356 286 : if (fstat(data->fd, &sbuf)) {
357 0 : return FAILURE;
358 : }
359 :
360 286 : data->st_size = *vallen = sbuf.st_size;
361 :
362 286 : if (sbuf.st_size == 0) {
363 272 : *val = STR_EMPTY_ALLOC();
364 272 : return SUCCESS;
365 : }
366 :
367 14 : *val = emalloc(sbuf.st_size);
368 :
369 : #if defined(HAVE_PREAD)
370 14 : 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 14 : if (n != sbuf.st_size) {
377 0 : if (n == -1) {
378 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "read failed: %s (%d)", strerror(errno), errno);
379 : } else {
380 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "read returned less bytes than requested");
381 : }
382 0 : efree(*val);
383 0 : return FAILURE;
384 : }
385 :
386 14 : return SUCCESS;
387 : }
388 :
389 : PS_WRITE_FUNC(files)
390 57 : {
391 : long n;
392 57 : PS_FILES_DATA;
393 :
394 57 : ps_files_open(data, key TSRMLS_CC);
395 57 : if (data->fd < 0) {
396 1 : return FAILURE;
397 : }
398 :
399 : /* Truncate file if the amount of new data is smaller than the existing data set. */
400 :
401 56 : if (vallen < (int)data->st_size) {
402 2 : ftruncate(data->fd, 0);
403 : }
404 :
405 : #if defined(HAVE_PWRITE)
406 56 : 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 56 : if (n != vallen) {
413 0 : if (n == -1) {
414 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "write failed: %s (%d)", strerror(errno), errno);
415 : } else {
416 0 : php_error_docref(NULL TSRMLS_CC, E_WARNING, "write wrote less bytes than requested");
417 : }
418 0 : return FAILURE;
419 : }
420 :
421 56 : return SUCCESS;
422 : }
423 :
424 : PS_DESTROY_FUNC(files)
425 232 : {
426 : char buf[MAXPATHLEN];
427 232 : PS_FILES_DATA;
428 :
429 232 : if (!ps_files_path_create(buf, sizeof(buf), data, key)) {
430 1 : return FAILURE;
431 : }
432 :
433 231 : if (data->fd != -1) {
434 228 : ps_files_close(data);
435 :
436 228 : 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 8 : if (!VCWD_ACCESS(buf, F_OK)) {
440 0 : return FAILURE;
441 : }
442 : }
443 : }
444 :
445 231 : return SUCCESS;
446 : }
447 :
448 : PS_GC_FUNC(files)
449 4 : {
450 4 : 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 4 : if (data->dirdepth == 0) {
457 4 : *nrdels = ps_files_cleanup_dir(data->basedir, maxlifetime TSRMLS_CC);
458 : }
459 :
460 4 : 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 : */
|