/src/php-src/main/php_open_temporary_file.c
Line | Count | Source (jump to first uncovered line) |
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 | | |
92 | | static int php_do_open_temporary_file(const char *path, const char *pfx, zend_string **opened_path_p) |
93 | 0 | { |
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 | 0 | char opened_path[MAXPATHLEN]; |
100 | 0 | char *trailing_slash; |
101 | 0 | #endif |
102 | 0 | uint64_t random; |
103 | 0 | char *random_prefix; |
104 | 0 | char *p; |
105 | 0 | size_t len; |
106 | 0 | char cwd[MAXPATHLEN]; |
107 | 0 | cwd_state new_state; |
108 | 0 | 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 | 0 | if (!path || !path[0]) { |
118 | 0 | return -1; |
119 | 0 | } |
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 | 0 | if (!VCWD_GETCWD(cwd, MAXPATHLEN)) { |
129 | 0 | cwd[0] = '\0'; |
130 | 0 | } |
131 | |
|
132 | 0 | new_state.cwd = estrdup(cwd); |
133 | 0 | new_state.cwd_length = strlen(cwd); |
134 | |
|
135 | 0 | if (virtual_file_ex(&new_state, path, NULL, CWD_REALPATH)) { |
136 | 0 | efree(new_state.cwd); |
137 | 0 | return -1; |
138 | 0 | } |
139 | | |
140 | | /* Extend the prefix to increase randomness */ |
141 | 0 | if (php_random_bytes_silent(&random, sizeof(random)) == FAILURE) { |
142 | 0 | random = php_random_generate_fallback_seed(); |
143 | 0 | } |
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 | 0 | len = strlen(pfx) + 13 /* log(2**64)/log(strlen(base32alphabet)) */ + 1; |
148 | 0 | random_prefix = emalloc(len); |
149 | 0 | p = zend_mempcpy(random_prefix, pfx, strlen(pfx)); |
150 | 0 | while (p + 1 < random_prefix + len) { |
151 | 0 | *p = base32alphabet[random % strlen(base32alphabet)]; |
152 | 0 | p++; |
153 | 0 | random /= strlen(base32alphabet); |
154 | 0 | } |
155 | 0 | *p = '\0'; |
156 | |
|
157 | 0 | #ifndef PHP_WIN32 |
158 | 0 | if (IS_SLASH(new_state.cwd[new_state.cwd_length - 1])) { |
159 | 0 | trailing_slash = ""; |
160 | 0 | } else { |
161 | 0 | trailing_slash = "/"; |
162 | 0 | } |
163 | |
|
164 | 0 | if (snprintf(opened_path, MAXPATHLEN, "%s%s%sXXXXXX", new_state.cwd, trailing_slash, random_prefix) >= MAXPATHLEN) { |
165 | 0 | efree(random_prefix); |
166 | 0 | efree(new_state.cwd); |
167 | 0 | return -1; |
168 | 0 | } |
169 | 0 | #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 | 0 | 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 | 0 | if (fd != -1 && opened_path_p) { |
223 | 0 | *opened_path_p = zend_string_init(opened_path, strlen(opened_path), 0); |
224 | 0 | } |
225 | 0 | #endif |
226 | 0 | efree(new_state.cwd); |
227 | 0 | efree(random_prefix); |
228 | 0 | return fd; |
229 | 0 | } |
230 | | /* }}} */ |
231 | | |
232 | | /* |
233 | | * Determine where to place temporary files. |
234 | | */ |
235 | | PHPAPI const char* php_get_temporary_directory(void) |
236 | 0 | { |
237 | | /* Did we determine the temporary directory already? */ |
238 | 0 | if (PG(php_sys_temp_dir)) { |
239 | 0 | return PG(php_sys_temp_dir); |
240 | 0 | } |
241 | | |
242 | | /* Is there a temporary directory "sys_temp_dir" in .ini defined? */ |
243 | 0 | { |
244 | 0 | char *sys_temp_dir = PG(sys_temp_dir); |
245 | 0 | if (sys_temp_dir) { |
246 | 0 | size_t len = strlen(sys_temp_dir); |
247 | 0 | if (len >= 2 && sys_temp_dir[len - 1] == DEFAULT_SLASH) { |
248 | 0 | PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len - 1); |
249 | 0 | return PG(php_sys_temp_dir); |
250 | 0 | } else if (len >= 1 && sys_temp_dir[len - 1] != DEFAULT_SLASH) { |
251 | 0 | PG(php_sys_temp_dir) = estrndup(sys_temp_dir, len); |
252 | 0 | return PG(php_sys_temp_dir); |
253 | 0 | } |
254 | 0 | } |
255 | 0 | } |
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 | 0 | { |
284 | 0 | char* s = getenv("TMPDIR"); |
285 | 0 | if (s && *s) { |
286 | 0 | size_t len = strlen(s); |
287 | |
|
288 | 0 | if (s[len - 1] == DEFAULT_SLASH) { |
289 | 0 | PG(php_sys_temp_dir) = estrndup(s, len - 1); |
290 | 0 | } else { |
291 | 0 | PG(php_sys_temp_dir) = estrndup(s, len); |
292 | 0 | } |
293 | |
|
294 | 0 | return PG(php_sys_temp_dir); |
295 | 0 | } |
296 | 0 | } |
297 | 0 | #ifdef P_tmpdir |
298 | | /* Use the standard default temporary directory. */ |
299 | 0 | if (P_tmpdir) { |
300 | 0 | PG(php_sys_temp_dir) = estrdup(P_tmpdir); |
301 | 0 | return PG(php_sys_temp_dir); |
302 | 0 | } |
303 | 0 | #endif |
304 | | /* Shouldn't ever(!) end up here ... last ditch default. */ |
305 | 0 | PG(php_sys_temp_dir) = estrdup("/tmp"); |
306 | 0 | return PG(php_sys_temp_dir); |
307 | 0 | #endif |
308 | 0 | } |
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 | | */ |
317 | | PHPAPI int php_open_temporary_fd_ex(const char *dir, const char *pfx, zend_string **opened_path_p, uint32_t flags) |
318 | 0 | { |
319 | 0 | int fd; |
320 | 0 | const char *temp_dir; |
321 | |
|
322 | 0 | if (!pfx) { |
323 | 0 | pfx = "tmp."; |
324 | 0 | } |
325 | 0 | if (opened_path_p) { |
326 | 0 | *opened_path_p = NULL; |
327 | 0 | } |
328 | |
|
329 | 0 | if (!dir || *dir == '\0') { |
330 | 0 | def_tmp: |
331 | 0 | temp_dir = php_get_temporary_directory(); |
332 | |
|
333 | 0 | if (temp_dir && |
334 | 0 | *temp_dir != '\0' && |
335 | 0 | (!(flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_FALLBACK) || !php_check_open_basedir(temp_dir))) { |
336 | 0 | return php_do_open_temporary_file(temp_dir, pfx, opened_path_p); |
337 | 0 | } else { |
338 | 0 | return -1; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | 0 | if ((flags & PHP_TMP_FILE_OPEN_BASEDIR_CHECK_ON_EXPLICIT_DIR) && php_check_open_basedir(dir)) { |
343 | 0 | return -1; |
344 | 0 | } |
345 | | |
346 | | /* Try the directory given as parameter. */ |
347 | 0 | fd = php_do_open_temporary_file(dir, pfx, opened_path_p); |
348 | 0 | if (fd == -1) { |
349 | | /* Use default temporary directory. */ |
350 | 0 | if (!(flags & PHP_TMP_FILE_SILENT)) { |
351 | 0 | php_error_docref(NULL, E_NOTICE, "file created in the system's temporary directory"); |
352 | 0 | } |
353 | 0 | goto def_tmp; |
354 | 0 | } |
355 | 0 | return fd; |
356 | 0 | } |
357 | | |
358 | | PHPAPI int php_open_temporary_fd(const char *dir, const char *pfx, zend_string **opened_path_p) |
359 | 0 | { |
360 | 0 | return php_open_temporary_fd_ex(dir, pfx, opened_path_p, PHP_TMP_FILE_DEFAULT); |
361 | 0 | } |
362 | | |
363 | | PHPAPI FILE *php_open_temporary_file(const char *dir, const char *pfx, zend_string **opened_path_p) |
364 | 0 | { |
365 | 0 | FILE *fp; |
366 | 0 | int fd = php_open_temporary_fd(dir, pfx, opened_path_p); |
367 | |
|
368 | 0 | if (fd == -1) { |
369 | 0 | return NULL; |
370 | 0 | } |
371 | | |
372 | 0 | fp = fdopen(fd, "r+b"); |
373 | 0 | if (fp == NULL) { |
374 | 0 | close(fd); |
375 | 0 | } |
376 | |
|
377 | 0 | return fp; |
378 | 0 | } |
379 | | /* }}} */ |