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