xref: /PHP-8.4/main/php_open_temporary_file.c (revision b4325d61)
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