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