1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Zeev Suraski <zeev@php.net> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php.h"
18 #include "zend_long.h"
19 #include "php_open_temporary_file.h"
20 #include "ext/random/php_random.h"
21 #include "zend_operators.h"
22
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27
28 #ifdef PHP_WIN32
29 #define O_RDONLY _O_RDONLY
30 #include "win32/param.h"
31 #include "win32/winutil.h"
32 #else
33 #include <sys/param.h>
34 #include <sys/socket.h>
35 #include <netinet/in.h>
36 #include <netdb.h>
37 #ifdef HAVE_ARPA_INET_H
38 #include <arpa/inet.h>
39 #endif
40 #endif
41 #ifdef HAVE_SYS_TIME_H
42 #include <sys/time.h>
43 #endif
44
45 #ifdef HAVE_SYS_FILE_H
46 #include <sys/file.h>
47 #endif
48
49 #if !defined(P_tmpdir)
50 #define P_tmpdir ""
51 #endif
52
53 /* {{{ php_do_open_temporary_file */
54
55 /* Loosely based on a tempnam() implementation by UCLA */
56
57 /*
58 * Copyright (c) 1988, 1993
59 * The Regents of the University of California. All rights reserved.
60 *
61 * Redistribution and use in source and binary forms, with or without
62 * modification, are permitted provided that the following conditions
63 * are met:
64 * 1. Redistributions of source code must retain the above copyright
65 * notice, this list of conditions and the following disclaimer.
66 * 2. Redistributions in binary form must reproduce the above copyright
67 * notice, this list of conditions and the following disclaimer in the
68 * documentation and/or other materials provided with the distribution.
69 * 3. All advertising materials mentioning features or use of this software
70 * must display the following acknowledgement:
71 * This product includes software developed by the University of
72 * California, Berkeley and its contributors.
73 * 4. Neither the name of the University nor the names of its contributors
74 * may be used to endorse or promote products derived from this software
75 * without specific prior written permission.
76 *
77 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
78 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
80 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
81 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
82 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
83 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
84 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
85 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
86 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
87 * SUCH DAMAGE.
88 */
89
90 static const char base32alphabet[] = "0123456789abcdefghijklmnopqrstuv";
91
php_do_open_temporary_file(const char * path,const char * pfx,zend_string ** opened_path_p)92 static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p)
93 {
94 #ifdef PHP_WIN32
95 char *opened_path = NULL;
96 size_t opened_path_len;
97 wchar_t *cwdw, *random_prefix_w, pathw[MAXPATHLEN];
98 #else
99 char opened_path[MAXPATHLEN];
100 char *trailing_slash;
101 #endif
102 uint64_t random;
103 char *random_prefix;
104 char *p;
105 size_t len;
106 char cwd[MAXPATHLEN];
107 cwd_state new_state;
108 int fd = -1;
109 #ifndef HAVE_MKSTEMP
110 int open_flags = O_CREAT | O_TRUNC | O_RDWR
111 #ifdef PHP_WIN32
112 | _O_BINARY
113 #endif
114 ;
115 #endif
116
117 if (!path || !path[0]) {
118 return -1;
119 }
120
121 #ifdef PHP_WIN32
122 if (!php_win32_check_trailing_space(pfx, strlen(pfx))) {
123 SetLastError(ERROR_INVALID_NAME);
124 return -1;
125 }
126 #endif
127
128 if (!VCWD_GETCWD(cwd, MAXPATHLEN)) {
129 cwd[0] = '\0';
130 }
131
132 new_state.cwd = estrdup(cwd);
133 new_state.cwd_length = strlen(cwd);
134
135 if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) {
136 efree(new_state.cwd);
137 return -1;
138 }
139
140 /* Extend the prefix to increase randomness */
141 if (php_random_bytes_silent(&random, sizeof(random)) == FAILURE) {
142 random = php_random_generate_fallback_seed();
143 }
144
145 /* Use a compact encoding to not increase the path len too much, but do not
146 * mix case to avoid losing randomness on case-insensitive file systems */
147 len = strlen(pfx) + 13 /* log(2**64)/log(strlen(base32alphabet)) */ + 1;
148 random_prefix = emalloc(len);
149 p = zend_mempcpy(random_prefix, pfx, strlen(pfx));
150 while (p + 1 < random_prefix + len) {
151 *p = base32alphabet[random % strlen(base32alphabet)];
152 p++;
153 random /= strlen(base32alphabet);
154 }
155 *p = '\0';
156
157 #ifndef PHP_WIN32
158 if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) {
159 trailing_slash = "";
160 } else {
161 trailing_slash = "/";
162 }
163
164 if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, random_prefix) >= MAXPATHLEN) {
165 efree(random_prefix);
166 efree(new_state.cwd);
167 return -1;
168 }
169 #endif
170
171 #ifdef PHP_WIN32
172 cwdw = php_win32_ioutil_any_to_w(new_state.cwd);
173 random_prefix_w = php_win32_ioutil_any_to_w(random_prefix);
174 if (!cwdw || !random_prefix_w) {
175 free(cwdw);
176 free(random_prefix_w);
177 efree(random_prefix);
178 efree(new_state.cwd);
179 return -1;
180 }
181
182 if (GetTempFileNameW(cwdw, random_prefix_w, 0, pathw)) {
183 opened_path = php_win32_ioutil_conv_w_to_any(pathw, PHP_WIN32_CP_IGNORE_LEN, &opened_path_len);
184 if (!opened_path || opened_path_len >= MAXPATHLEN) {
185 free(cwdw);
186 free(random_prefix_w);
187 efree(random_prefix);
188 efree(new_state.cwd);
189 return -1;
190 }
191 assert(strlen(opened_path) == opened_path_len);
192
193 /* Some versions of windows set the temp file to be read-only,
194 * which means that opening it will fail... */
195 if (VCWD_CHMOD(opened_path, 0600)) {
196 free(cwdw);
197 free(random_prefix_w);
198 efree(random_prefix);
199 efree(new_state.cwd);
200 free(opened_path);
201 return -1;
202 }
203 fd = VCWD_OPEN_MODE(opened_path, open_flags, 0600);
204 }
205
206 free(cwdw);
207 free(random_prefix_w);
208 #elif defined(HAVE_MKSTEMP)
209 fd = mkstemp(opened_path);
210 #else
211 if (mktemp(opened_path)) {
212 fd = VCWD_OPEN(opened_path, open_flags);
213 }
214 #endif
215
216 #ifdef PHP_WIN32
217 if (fd != -1 && opened_path_p) {
218 *opened_path_p = zend_string_init(opened_path, opened_path_len, 0);
219 }
220 free(opened_path);
221 #else
222 if (fd != -1 && opened_path_p) {
223 *opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0);
224 }
225 #endif
226 efree(new_state.cwd);
227 efree(random_prefix);
228 return fd;
229 }
230 /* }}} */
231
232 /*
233 * Determine where to place temporary files.
234 */
php_get_temporary_directory(void)235 PHPAPI const char* php_get_temporary_directory(void)
236 {
237 /* Did we determine the temporary directory already? */
238 if (PG(php_sys_temp_dir)) {
239 return PG(php_sys_temp_dir);
240 }
241
242 /* Is there a temporary directory "sys_temp_dir" in .ini defined? */
243 {
244 char *sys_temp_dir = PG(sys_temp_dir);
245 if (sys_temp_dir) {
246 size_t len = strlen(sys_temp_dir);
247 if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) {
248 PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1);
249 return PG(php_sys_temp_dir);
250 } else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) {
251 PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len);
252 return PG(php_sys_temp_dir);
253 }
254 }
255 }
256
257 #ifdef PHP_WIN32
258 /* We can't count on the environment variables TEMP or TMP,
259 * and so must make the Win32 API call to get the default
260 * directory for temporary files. Note this call checks
261 * the environment values TMP and TEMP (in order) first.
262 */
263 {
264 wchar_t sTemp[MAXPATHLEN];
265 char *tmp;
266 size_t len = GetTempPathW(MAXPATHLEN, sTemp);
267
268 if (!len) {
269 return NULL;
270 }
271
272 if (NULL == (tmp = php_win32_ioutil_conv_w_to_any(sTemp, len, &len))) {
273 return NULL;
274 }
275
276 PG(php_sys_temp_dir) = estrndup(tmp, len - 1);
277
278 free(tmp);
279 return PG(php_sys_temp_dir);
280 }
281 #else
282 /* On Unix use the (usual) TMPDIR environment variable. */
283 {
284 char* s = getenv("TMPDIR");
285 if (s && *s) {
286 size_t len = strlen(s);
287
288 if (s[len - 1] == DEFAULT_SLASH) {
289 PG(php_sys_temp_dir) = estrndup(s, len - 1);
290 } else {
291 PG(php_sys_temp_dir) = estrndup(s, len);
292 }
293
294 return PG(php_sys_temp_dir);
295 }
296 }
297 #ifdef P_tmpdir
298 /* Use the standard default temporary directory. */
299 if (P_tmpdir) {
300 PG(php_sys_temp_dir) = estrdup(P_tmpdir);
301 return PG(php_sys_temp_dir);
302 }
303 #endif
304 /* Shouldn't ever(!) end up here ... last ditch default. */
305 PG(php_sys_temp_dir) = estrdup("/tmp");
306 return PG(php_sys_temp_dir);
307 #endif
308 }
309
310 /* {{{ php_open_temporary_file
311 *
312 * Unlike tempnam(), the supplied dir argument takes precedence
313 * over the TMPDIR environment variable
314 * This function should do its best to return a file pointer to a newly created
315 * unique file, on every platform.
316 */
php_open_temporary_fd_ex(const char * dir,const char * pfx,zend_string ** opened_path_p,uint32_t flags)317 PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags)
318 {
319 int fd;
320 const char *temp_dir;
321
322 if (!pfx) {
323 pfx = "tmp.";
324 }
325 if (opened_path_p) {
326 *opened_path_p = NULL;
327 }
328
329 if (!dir || *dir == '\0') {
330 def_tmp:
331 temp_dir = php_get_temporary_directory();
332
333 if (temp_dir &&
334 *temp_dir != '\0' &&
335 (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) {
336 return php_do_open_temporary_file(temp_dir, pfx, opened_path_p);
337 } else {
338 return -1;
339 }
340 }
341
342 if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) {
343 return -1;
344 }
345
346 /* Try the directory given as parameter. */
347 fd = php_do_open_temporary_file(dir, pfx, opened_path_p);
348 if (fd == -1) {
349 /* Use default temporary directory. */
350 if (!(flags & PHP_TMP_FILE_SILENT)) {
351 php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory");
352 }
353 goto def_tmp;
354 }
355 return fd;
356 }
357
php_open_temporary_fd(const char * dir,const char * pfx,zend_string ** opened_path_p)358 PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p)
359 {
360 return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT);
361 }
362
php_open_temporary_file(const char * dir,const char * pfx,zend_string ** opened_path_p)363 PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p)
364 {
365 FILE *fp;
366 int fd = php_open_temporary_fd(dir, pfx, opened_path_p);
367
368 if (fd == -1) {
369 return NULL;
370 }
371
372 fp = fdopen(fd, "r+b");
373 if (fp == NULL) {
374 close(fd);
375 }
376
377 return fp;
378 }
379 /* }}} */
380