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: Wez Furlong <wez@thebrainroom.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php.h"
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <signal.h>
21 #include "php_string.h"
22 #include "ext/standard/head.h"
23 #include "ext/standard/basic_functions.h"
24 #include "ext/standard/file.h"
25 #include "exec.h"
26 #include "php_globals.h"
27 #include "SAPI.h"
28 #include "main/php_network.h"
29 #include "zend_smart_str.h"
30
31 #ifdef HAVE_SYS_WAIT_H
32 #include <sys/wait.h>
33 #endif
34
35 #ifdef HAVE_FCNTL_H
36 #include <fcntl.h>
37 #endif
38
39 #ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
40 /* Only defined on glibc >= 2.29, FreeBSD CURRENT, musl >= 1.1.24,
41 * MacOS Catalina or later..
42 * It should be posible to modify this so it is also
43 * used in older systems when $cwd == NULL but care must be taken
44 * as at least glibc < 2.24 has a legacy implementation known
45 * to be really buggy.
46 */
47 #include <spawn.h>
48 #define USE_POSIX_SPAWN
49 #endif
50
51 /* This symbol is defined in ext/standard/config.m4.
52 * Essentially, it is set if you HAVE_FORK || PHP_WIN32
53 * Other platforms may modify that configure check and add suitable #ifdefs
54 * around the alternate code. */
55 #ifdef PHP_CAN_SUPPORT_PROC_OPEN
56
57 #ifdef HAVE_OPENPTY
58 # ifdef HAVE_PTY_H
59 # include <pty.h>
60 # elif defined(__FreeBSD__)
61 /* FreeBSD defines `openpty` in <libutil.h> */
62 # include <libutil.h>
63 # elif defined(__NetBSD__) || defined(__DragonFly__)
64 /* On recent NetBSD/DragonFlyBSD releases the emalloc, estrdup ... calls had been introduced in libutil */
65 # if defined(__NetBSD__)
66 # include <sys/termios.h>
67 # else
68 # include <termios.h>
69 # endif
70 extern int openpty(int *, int *, char *, struct termios *, struct winsize *);
71 # elif defined(__sun)
72 # include <termios.h>
73 # else
74 /* Mac OS X (and some BSDs) define `openpty` in <util.h> */
75 # include <util.h>
76 # endif
77 #elif defined(__sun)
78 # include <fcntl.h>
79 # include <stropts.h>
80 # include <termios.h>
81 # define HAVE_OPENPTY 1
82
83 /* Solaris before version 11.4 and Illumos do not have any openpty implementation */
openpty(int * master,int * slave,char * name,struct termios * termp,struct winsize * winp)84 int openpty(int *master, int *slave, char *name, struct termios *termp, struct winsize *winp)
85 {
86 int fd, sd;
87 const char *slaveid;
88
89 assert(master);
90 assert(slave);
91
92 sd = *master = *slave = -1;
93 fd = open("/dev/ptmx", O_NOCTTY|O_RDWR);
94 if (fd == -1) {
95 return -1;
96 }
97 /* Checking if we can have to the pseudo terminal */
98 if (grantpt(fd) != 0 || unlockpt(fd) != 0) {
99 goto fail;
100 }
101 slaveid = ptsname(fd);
102 if (!slaveid) {
103 goto fail;
104 }
105
106 /* Getting the slave path and pushing pseudo terminal */
107 sd = open(slaveid, O_NOCTTY|O_RDONLY);
108 if (sd == -1 || ioctl(sd, I_PUSH, "ptem") == -1) {
109 goto fail;
110 }
111 if (termp) {
112 if (tcgetattr(sd, termp) < 0) {
113 goto fail;
114 }
115 }
116 if (winp) {
117 if (ioctl(sd, TIOCSWINSZ, winp) == -1) {
118 goto fail;
119 }
120 }
121
122 *slave = sd;
123 *master = fd;
124 return 0;
125 fail:
126 if (sd != -1) {
127 close(sd);
128 }
129 if (fd != -1) {
130 close(fd);
131 }
132 return -1;
133 }
134 #endif
135
136 #include "proc_open.h"
137
138 static int le_proc_open; /* Resource number for `proc` resources */
139
140 /* {{{ _php_array_to_envp
141 * Process the `environment` argument to `proc_open`
142 * Convert into data structures which can be passed to underlying OS APIs like `exec` on POSIX or
143 * `CreateProcessW` on Win32 */
_php_array_to_envp(zval * environment)144 static php_process_env _php_array_to_envp(zval *environment)
145 {
146 zval *element;
147 php_process_env env;
148 zend_string *key, *str;
149 #ifndef PHP_WIN32
150 char **ep;
151 #endif
152 char *p;
153 size_t sizeenv = 0;
154 HashTable *env_hash; /* temporary PHP array used as helper */
155
156 memset(&env, 0, sizeof(env));
157
158 if (!environment) {
159 return env;
160 }
161
162 uint32_t cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
163
164 if (cnt < 1) {
165 #ifndef PHP_WIN32
166 env.envarray = (char **) ecalloc(1, sizeof(char *));
167 #endif
168 env.envp = (char *) ecalloc(4, 1);
169 return env;
170 }
171
172 ALLOC_HASHTABLE(env_hash);
173 zend_hash_init(env_hash, cnt, NULL, NULL, 0);
174
175 /* first, we have to get the size of all the elements in the hash */
176 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) {
177 str = zval_get_string(element);
178
179 if (ZSTR_LEN(str) == 0) {
180 zend_string_release_ex(str, 0);
181 continue;
182 }
183
184 sizeenv += ZSTR_LEN(str) + 1;
185
186 if (key && ZSTR_LEN(key)) {
187 sizeenv += ZSTR_LEN(key) + 1;
188 zend_hash_add_ptr(env_hash, key, str);
189 } else {
190 zend_hash_next_index_insert_ptr(env_hash, str);
191 }
192 } ZEND_HASH_FOREACH_END();
193
194 #ifndef PHP_WIN32
195 ep = env.envarray = (char **) ecalloc(cnt + 1, sizeof(char *));
196 #endif
197 p = env.envp = (char *) ecalloc(sizeenv + 4, 1);
198
199 ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, key, str) {
200 #ifndef PHP_WIN32
201 *ep = p;
202 ++ep;
203 #endif
204
205 if (key) {
206 p = zend_mempcpy(p, ZSTR_VAL(key), ZSTR_LEN(key));
207 *p++ = '=';
208 }
209
210 p = zend_mempcpy(p, ZSTR_VAL(str), ZSTR_LEN(str));
211 *p++ = '\0';
212 zend_string_release_ex(str, 0);
213 } ZEND_HASH_FOREACH_END();
214
215 assert((uint32_t)(p - env.envp) <= sizeenv);
216
217 zend_hash_destroy(env_hash);
218 FREE_HASHTABLE(env_hash);
219
220 return env;
221 }
222 /* }}} */
223
224 /* {{{ _php_free_envp
225 * Free the structures allocated by `_php_array_to_envp` */
_php_free_envp(php_process_env env)226 static void _php_free_envp(php_process_env env)
227 {
228 #ifndef PHP_WIN32
229 if (env.envarray) {
230 efree(env.envarray);
231 }
232 #endif
233 if (env.envp) {
234 efree(env.envp);
235 }
236 }
237 /* }}} */
238
239 #if HAVE_SYS_WAIT_H
waitpid_cached(php_process_handle * proc,int * wait_status,int options)240 static pid_t waitpid_cached(php_process_handle *proc, int *wait_status, int options)
241 {
242 if (proc->has_cached_exit_wait_status) {
243 *wait_status = proc->cached_exit_wait_status_value;
244 return proc->child;
245 }
246
247 pid_t wait_pid = waitpid(proc->child, wait_status, options);
248
249 /* The "exit" status is the final status of the process.
250 * If we were to cache the status unconditionally,
251 * we would return stale statuses in the future after the process continues. */
252 if (wait_pid > 0 && WIFEXITED(*wait_status)) {
253 proc->has_cached_exit_wait_status = true;
254 proc->cached_exit_wait_status_value = *wait_status;
255 }
256
257 return wait_pid;
258 }
259 #endif
260
261 /* {{{ proc_open_rsrc_dtor
262 * Free `proc` resource, either because all references to it were dropped or because `pclose` or
263 * `proc_close` were called */
proc_open_rsrc_dtor(zend_resource * rsrc)264 static void proc_open_rsrc_dtor(zend_resource *rsrc)
265 {
266 php_process_handle *proc = (php_process_handle*)rsrc->ptr;
267 #ifdef PHP_WIN32
268 DWORD wstatus;
269 #elif HAVE_SYS_WAIT_H
270 int wstatus;
271 int waitpid_options = 0;
272 pid_t wait_pid;
273 #endif
274
275 /* Close all handles to avoid a deadlock */
276 for (int i = 0; i < proc->npipes; i++) {
277 if (proc->pipes[i] != NULL) {
278 GC_DELREF(proc->pipes[i]);
279 zend_list_close(proc->pipes[i]);
280 proc->pipes[i] = NULL;
281 }
282 }
283
284 /* `pclose_wait` tells us: Are we freeing this resource because `pclose` or `proc_close` were
285 * called? If so, we need to wait until the child process exits, because its exit code is
286 * needed as the return value of those functions.
287 * But if we're freeing the resource because of GC, don't wait. */
288 #ifdef PHP_WIN32
289 if (FG(pclose_wait)) {
290 WaitForSingleObject(proc->childHandle, INFINITE);
291 }
292 GetExitCodeProcess(proc->childHandle, &wstatus);
293 if (wstatus == STILL_ACTIVE) {
294 FG(pclose_ret) = -1;
295 } else {
296 FG(pclose_ret) = wstatus;
297 }
298 CloseHandle(proc->childHandle);
299
300 #elif HAVE_SYS_WAIT_H
301 if (!FG(pclose_wait)) {
302 waitpid_options = WNOHANG;
303 }
304 do {
305 wait_pid = waitpid_cached(proc, &wstatus, waitpid_options);
306 } while (wait_pid == -1 && errno == EINTR);
307
308 if (wait_pid <= 0) {
309 FG(pclose_ret) = -1;
310 } else {
311 if (WIFEXITED(wstatus)) {
312 wstatus = WEXITSTATUS(wstatus);
313 }
314 FG(pclose_ret) = wstatus;
315 }
316
317 #else
318 FG(pclose_ret) = -1;
319 #endif
320
321 _php_free_envp(proc->env);
322 efree(proc->pipes);
323 zend_string_release_ex(proc->command, false);
324 efree(proc);
325 }
326 /* }}} */
327
328 /* {{{ PHP_MINIT_FUNCTION(proc_open) */
PHP_MINIT_FUNCTION(proc_open)329 PHP_MINIT_FUNCTION(proc_open)
330 {
331 le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process",
332 module_number);
333 return SUCCESS;
334 }
335 /* }}} */
336
337 /* {{{ Kill a process opened by `proc_open` */
PHP_FUNCTION(proc_terminate)338 PHP_FUNCTION(proc_terminate)
339 {
340 zval *zproc;
341 php_process_handle *proc;
342 zend_long sig_no = SIGTERM;
343
344 ZEND_PARSE_PARAMETERS_START(1, 2)
345 Z_PARAM_RESOURCE(zproc)
346 Z_PARAM_OPTIONAL
347 Z_PARAM_LONG(sig_no)
348 ZEND_PARSE_PARAMETERS_END();
349
350 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
351 if (proc == NULL) {
352 RETURN_THROWS();
353 }
354
355 #ifdef PHP_WIN32
356 RETURN_BOOL(TerminateProcess(proc->childHandle, 255));
357 #else
358 RETURN_BOOL(kill(proc->child, sig_no) == 0);
359 #endif
360 }
361 /* }}} */
362
363 /* {{{ Close a process opened by `proc_open` */
PHP_FUNCTION(proc_close)364 PHP_FUNCTION(proc_close)
365 {
366 zval *zproc;
367 php_process_handle *proc;
368
369 ZEND_PARSE_PARAMETERS_START(1, 1)
370 Z_PARAM_RESOURCE(zproc)
371 ZEND_PARSE_PARAMETERS_END();
372
373 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
374 if (proc == NULL) {
375 RETURN_THROWS();
376 }
377
378 FG(pclose_wait) = 1; /* See comment in `proc_open_rsrc_dtor` */
379 zend_list_close(Z_RES_P(zproc));
380 FG(pclose_wait) = 0;
381 RETURN_LONG(FG(pclose_ret));
382 }
383 /* }}} */
384
385 /* {{{ Get information about a process opened by `proc_open` */
PHP_FUNCTION(proc_get_status)386 PHP_FUNCTION(proc_get_status)
387 {
388 zval *zproc;
389 php_process_handle *proc;
390 #ifdef PHP_WIN32
391 DWORD wstatus;
392 #elif HAVE_SYS_WAIT_H
393 int wstatus;
394 pid_t wait_pid;
395 #endif
396 bool running = 1, signaled = 0, stopped = 0;
397 int exitcode = -1, termsig = 0, stopsig = 0;
398
399 ZEND_PARSE_PARAMETERS_START(1, 1)
400 Z_PARAM_RESOURCE(zproc)
401 ZEND_PARSE_PARAMETERS_END();
402
403 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
404 if (proc == NULL) {
405 RETURN_THROWS();
406 }
407
408 array_init(return_value);
409 add_assoc_str(return_value, "command", zend_string_copy(proc->command));
410 add_assoc_long(return_value, "pid", (zend_long)proc->child);
411
412 #ifdef PHP_WIN32
413 GetExitCodeProcess(proc->childHandle, &wstatus);
414 running = wstatus == STILL_ACTIVE;
415 exitcode = running ? -1 : wstatus;
416
417 /* The status is always available on Windows and will always read the same,
418 * even if the child has already exited. This is because the result stays available
419 * until the child handle is closed. Hence no caching is used on Windows. */
420 add_assoc_bool(return_value, "cached", false);
421 #elif HAVE_SYS_WAIT_H
422 wait_pid = waitpid_cached(proc, &wstatus, WNOHANG|WUNTRACED);
423
424 if (wait_pid == proc->child) {
425 if (WIFEXITED(wstatus)) {
426 running = 0;
427 exitcode = WEXITSTATUS(wstatus);
428 }
429 if (WIFSIGNALED(wstatus)) {
430 running = 0;
431 signaled = 1;
432 termsig = WTERMSIG(wstatus);
433 }
434 if (WIFSTOPPED(wstatus)) {
435 stopped = 1;
436 stopsig = WSTOPSIG(wstatus);
437 }
438 } else if (wait_pid == -1) {
439 /* The only error which could occur here is ECHILD, which means that the PID we were
440 * looking for either does not exist or is not a child of this process */
441 running = 0;
442 }
443
444 add_assoc_bool(return_value, "cached", proc->has_cached_exit_wait_status);
445 #endif
446
447 add_assoc_bool(return_value, "running", running);
448 add_assoc_bool(return_value, "signaled", signaled);
449 add_assoc_bool(return_value, "stopped", stopped);
450 add_assoc_long(return_value, "exitcode", exitcode);
451 add_assoc_long(return_value, "termsig", termsig);
452 add_assoc_long(return_value, "stopsig", stopsig);
453 }
454 /* }}} */
455
456 #ifdef PHP_WIN32
457
458 /* We use this to allow child processes to inherit handles
459 * One static instance can be shared and used for all calls to `proc_open`, since the values are
460 * never changed */
461 SECURITY_ATTRIBUTES php_proc_open_security = {
462 .nLength = sizeof(SECURITY_ATTRIBUTES),
463 .lpSecurityDescriptor = NULL,
464 .bInheritHandle = TRUE
465 };
466
467 # define pipe(pair) (CreatePipe(&pair[0], &pair[1], &php_proc_open_security, 0) ? 0 : -1)
468
469 # define COMSPEC_NT "cmd.exe"
470
dup_handle(HANDLE src,BOOL inherit,BOOL closeorig)471 static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
472 {
473 HANDLE copy, self = GetCurrentProcess();
474
475 if (!DuplicateHandle(self, src, self, ©, 0, inherit, DUPLICATE_SAME_ACCESS |
476 (closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
477 return NULL;
478 return copy;
479 }
480
dup_fd_as_handle(int fd)481 static inline HANDLE dup_fd_as_handle(int fd)
482 {
483 return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE);
484 }
485
486 # define close_descriptor(fd) CloseHandle(fd)
487 #else /* !PHP_WIN32 */
488 # define close_descriptor(fd) close(fd)
489 #endif
490
491 /* Determines the type of a descriptor item. */
492 typedef enum _descriptor_type {
493 DESCRIPTOR_TYPE_STD,
494 DESCRIPTOR_TYPE_PIPE,
495 DESCRIPTOR_TYPE_SOCKET
496 } descriptor_type;
497
498 /* One instance of this struct is created for each item in `$descriptorspec` argument to `proc_open`
499 * They are used within `proc_open` and freed before it returns */
500 typedef struct _descriptorspec_item {
501 int index; /* desired FD # in child process */
502 descriptor_type type;
503 php_file_descriptor_t childend; /* FD # opened for use in child
504 * (will be copied to `index` in child) */
505 php_file_descriptor_t parentend; /* FD # opened for use in parent
506 * (for pipes only; will be 0 otherwise) */
507 int mode_flags; /* mode for opening FDs: r/o, r/w, binary (on Win32), etc */
508 } descriptorspec_item;
509
get_valid_arg_string(zval * zv,int elem_num)510 static zend_string *get_valid_arg_string(zval *zv, int elem_num) {
511 zend_string *str = zval_get_string(zv);
512 if (!str) {
513 return NULL;
514 }
515
516 if (elem_num == 1 && ZSTR_LEN(str) == 0) {
517 zend_value_error("First element must contain a non-empty program name");
518 zend_string_release(str);
519 return NULL;
520 }
521
522 if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) {
523 zend_value_error("Command array element %d contains a null byte", elem_num);
524 zend_string_release(str);
525 return NULL;
526 }
527
528 return str;
529 }
530
531 #ifdef PHP_WIN32
append_backslashes(smart_str * str,size_t num_bs)532 static void append_backslashes(smart_str *str, size_t num_bs)
533 {
534 for (size_t i = 0; i < num_bs; i++) {
535 smart_str_appendc(str, '\\');
536 }
537 }
538
539 const char *special_chars = "()!^\"<>&|%";
540
is_special_character_present(const zend_string * arg)541 static bool is_special_character_present(const zend_string *arg)
542 {
543 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
544 if (strchr(special_chars, ZSTR_VAL(arg)[i]) != NULL) {
545 return true;
546 }
547 }
548 return false;
549 }
550
551 /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
552 * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
append_win_escaped_arg(smart_str * str,zend_string * arg,bool is_cmd_argument)553 static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd_argument)
554 {
555 size_t num_bs = 0;
556 bool has_special_character = false;
557
558 if (is_cmd_argument) {
559 has_special_character = is_special_character_present(arg);
560 if (has_special_character) {
561 /* Escape double quote with ^ if executed by cmd.exe. */
562 smart_str_appendc(str, '^');
563 }
564 }
565 smart_str_appendc(str, '"');
566 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
567 char c = ZSTR_VAL(arg)[i];
568 if (c == '\\') {
569 num_bs++;
570 continue;
571 }
572
573 if (c == '"') {
574 /* Backslashes before " need to be doubled. */
575 num_bs = num_bs * 2 + 1;
576 }
577 append_backslashes(str, num_bs);
578 if (has_special_character && strchr(special_chars, c) != NULL) {
579 /* Escape special chars with ^ if executed by cmd.exe. */
580 smart_str_appendc(str, '^');
581 }
582 smart_str_appendc(str, c);
583 num_bs = 0;
584 }
585 append_backslashes(str, num_bs * 2);
586 if (has_special_character) {
587 /* Escape double quote with ^ if executed by cmd.exe. */
588 smart_str_appendc(str, '^');
589 }
590 smart_str_appendc(str, '"');
591 }
592
stricmp_end(const char * suffix,const char * str)593 static inline int stricmp_end(const char* suffix, const char* str) {
594 size_t suffix_len = strlen(suffix);
595 size_t str_len = strlen(str);
596
597 if (suffix_len > str_len) {
598 return -1; /* Suffix is longer than string, cannot match. */
599 }
600
601 /* Compare the end of the string with the suffix, ignoring case. */
602 return _stricmp(str + (str_len - suffix_len), suffix);
603 }
604
is_executed_by_cmd(const char * prog_name)605 static bool is_executed_by_cmd(const char *prog_name)
606 {
607 /* If program name is cmd.exe, then return true. */
608 if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0
609 || stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) {
610 return true;
611 }
612
613 /* Find the last occurrence of the directory separator (backslash or forward slash). */
614 char *last_separator = strrchr(prog_name, '\\');
615 char *last_separator_fwd = strrchr(prog_name, '/');
616 if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) {
617 last_separator = last_separator_fwd;
618 }
619
620 /* Find the last dot in the filename after the last directory separator. */
621 char *extension = NULL;
622 if (last_separator != NULL) {
623 extension = strrchr(last_separator, '.');
624 } else {
625 extension = strrchr(prog_name, '.');
626 }
627
628 if (extension == NULL || extension == prog_name) {
629 /* No file extension found, it is not batch file. */
630 return false;
631 }
632
633 /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */
634 return _stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0;
635 }
636
create_win_command_from_args(HashTable * args)637 static zend_string *create_win_command_from_args(HashTable *args)
638 {
639 smart_str str = {0};
640 zval *arg_zv;
641 bool is_prog_name = true;
642 bool is_cmd_execution = false;
643 int elem_num = 0;
644
645 ZEND_HASH_FOREACH_VAL(args, arg_zv) {
646 zend_string *arg_str = get_valid_arg_string(arg_zv, ++elem_num);
647 if (!arg_str) {
648 smart_str_free(&str);
649 return NULL;
650 }
651
652 if (is_prog_name) {
653 is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str));
654 } else {
655 smart_str_appendc(&str, ' ');
656 }
657
658 append_win_escaped_arg(&str, arg_str, !is_prog_name && is_cmd_execution);
659
660 is_prog_name = 0;
661 zend_string_release(arg_str);
662 } ZEND_HASH_FOREACH_END();
663 smart_str_0(&str);
664 return str.s;
665 }
666
667 /* Get a boolean option from the `other_options` array which can be passed to `proc_open`.
668 * (Currently, all options apply on Windows only.) */
get_option(zval * other_options,char * opt_name,size_t opt_name_len)669 static bool get_option(zval *other_options, char *opt_name, size_t opt_name_len)
670 {
671 HashTable *opt_ary = Z_ARRVAL_P(other_options);
672 zval *item = zend_hash_str_find_deref(opt_ary, opt_name, opt_name_len);
673 return item != NULL &&
674 (Z_TYPE_P(item) == IS_TRUE ||
675 ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item)));
676 }
677
678 /* Initialize STARTUPINFOW struct, used on Windows when spawning a process.
679 * Ref: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow */
init_startup_info(STARTUPINFOW * si,descriptorspec_item * descriptors,int ndesc)680 static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors, int ndesc)
681 {
682 memset(si, 0, sizeof(STARTUPINFOW));
683 si->cb = sizeof(STARTUPINFOW);
684 si->dwFlags = STARTF_USESTDHANDLES;
685
686 si->hStdInput = GetStdHandle(STD_INPUT_HANDLE);
687 si->hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
688 si->hStdError = GetStdHandle(STD_ERROR_HANDLE);
689
690 /* redirect stdin/stdout/stderr if requested */
691 for (int i = 0; i < ndesc; i++) {
692 switch (descriptors[i].index) {
693 case 0:
694 si->hStdInput = descriptors[i].childend;
695 break;
696 case 1:
697 si->hStdOutput = descriptors[i].childend;
698 break;
699 case 2:
700 si->hStdError = descriptors[i].childend;
701 break;
702 }
703 }
704 }
705
init_process_info(PROCESS_INFORMATION * pi)706 static void init_process_info(PROCESS_INFORMATION *pi)
707 {
708 memset(&pi, 0, sizeof(pi));
709 }
710
convert_command_to_use_shell(wchar_t ** cmdw,size_t cmdw_len)711 static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
712 {
713 size_t len = sizeof(COMSPEC_NT) + sizeof(" /s /c ") + cmdw_len + 3;
714 wchar_t *cmdw_shell = (wchar_t *)malloc(len * sizeof(wchar_t));
715
716 if (cmdw_shell == NULL) {
717 php_error_docref(NULL, E_WARNING, "Command conversion failed");
718 return FAILURE;
719 }
720
721 if (_snwprintf(cmdw_shell, len, L"%hs /s /c \"%s\"", COMSPEC_NT, *cmdw) == -1) {
722 free(cmdw_shell);
723 php_error_docref(NULL, E_WARNING, "Command conversion failed");
724 return FAILURE;
725 }
726
727 free(*cmdw);
728 *cmdw = cmdw_shell;
729
730 return SUCCESS;
731 }
732 #endif
733
734 /* Convert command parameter array passed as first argument to `proc_open` into command string */
get_command_from_array(HashTable * array,char *** argv,int num_elems)735 static zend_string* get_command_from_array(HashTable *array, char ***argv, int num_elems)
736 {
737 zval *arg_zv;
738 zend_string *command = NULL;
739 int i = 0;
740
741 *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
742
743 ZEND_HASH_FOREACH_VAL(array, arg_zv) {
744 zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
745 if (!arg_str) {
746 /* Terminate with NULL so exit_fail code knows how many entries to free */
747 (*argv)[i] = NULL;
748 if (command != NULL) {
749 zend_string_release_ex(command, false);
750 }
751 return NULL;
752 }
753
754 if (i == 0) {
755 command = zend_string_copy(arg_str);
756 }
757
758 (*argv)[i++] = estrdup(ZSTR_VAL(arg_str));
759 zend_string_release(arg_str);
760 } ZEND_HASH_FOREACH_END();
761
762 (*argv)[i] = NULL;
763 return command;
764 }
765
alloc_descriptor_array(HashTable * descriptorspec)766 static descriptorspec_item* alloc_descriptor_array(HashTable *descriptorspec)
767 {
768 uint32_t ndescriptors = zend_hash_num_elements(descriptorspec);
769 return ecalloc(ndescriptors, sizeof(descriptorspec_item));
770 }
771
get_string_parameter(zval * array,int index,char * param_name)772 static zend_string* get_string_parameter(zval *array, int index, char *param_name)
773 {
774 zval *array_item;
775 if ((array_item = zend_hash_index_find(Z_ARRVAL_P(array), index)) == NULL) {
776 zend_value_error("Missing %s", param_name);
777 return NULL;
778 }
779 return zval_try_get_string(array_item);
780 }
781
set_proc_descriptor_to_blackhole(descriptorspec_item * desc)782 static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc)
783 {
784 #ifdef PHP_WIN32
785 desc->childend = CreateFileA("nul", GENERIC_READ | GENERIC_WRITE,
786 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
787 if (desc->childend == NULL) {
788 php_error_docref(NULL, E_WARNING, "Failed to open nul");
789 return FAILURE;
790 }
791 #else
792 desc->childend = open("/dev/null", O_RDWR);
793 if (desc->childend < 0) {
794 php_error_docref(NULL, E_WARNING, "Failed to open /dev/null: %s", strerror(errno));
795 return FAILURE;
796 }
797 #endif
798 return SUCCESS;
799 }
800
set_proc_descriptor_to_pty(descriptorspec_item * desc,int * master_fd,int * slave_fd)801 static zend_result set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd, int *slave_fd)
802 {
803 #ifdef HAVE_OPENPTY
804 /* All FDs set to PTY in the child process will go to the slave end of the same PTY.
805 * Likewise, all the corresponding entries in `$pipes` in the parent will all go to the master
806 * end of the same PTY.
807 * If this is the first descriptorspec set to 'pty', find an available PTY and get master and
808 * slave FDs. */
809 if (*master_fd == -1) {
810 if (openpty(master_fd, slave_fd, NULL, NULL, NULL)) {
811 php_error_docref(NULL, E_WARNING, "Could not open PTY (pseudoterminal): %s", strerror(errno));
812 return FAILURE;
813 }
814 }
815
816 desc->type = DESCRIPTOR_TYPE_PIPE;
817 desc->childend = dup(*slave_fd);
818 desc->parentend = dup(*master_fd);
819 desc->mode_flags = O_RDWR;
820 return SUCCESS;
821 #else
822 php_error_docref(NULL, E_WARNING, "PTY (pseudoterminal) not supported on this system");
823 return FAILURE;
824 #endif
825 }
826
827 /* Mark the descriptor close-on-exec, so it won't be inherited by children */
make_descriptor_cloexec(php_file_descriptor_t fd)828 static php_file_descriptor_t make_descriptor_cloexec(php_file_descriptor_t fd)
829 {
830 #ifdef PHP_WIN32
831 return dup_handle(fd, FALSE, TRUE);
832 #else
833 #if defined(F_SETFD) && defined(FD_CLOEXEC)
834 fcntl(fd, F_SETFD, FD_CLOEXEC);
835 #endif
836 return fd;
837 #endif
838 }
839
set_proc_descriptor_to_pipe(descriptorspec_item * desc,zend_string * zmode)840 static zend_result set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *zmode)
841 {
842 php_file_descriptor_t newpipe[2];
843
844 if (pipe(newpipe)) {
845 php_error_docref(NULL, E_WARNING, "Unable to create pipe %s", strerror(errno));
846 return FAILURE;
847 }
848
849 desc->type = DESCRIPTOR_TYPE_PIPE;
850
851 if (!zend_string_starts_with_literal(zmode, "w")) {
852 desc->parentend = newpipe[1];
853 desc->childend = newpipe[0];
854 desc->mode_flags = O_WRONLY;
855 } else {
856 desc->parentend = newpipe[0];
857 desc->childend = newpipe[1];
858 desc->mode_flags = O_RDONLY;
859 }
860
861 desc->parentend = make_descriptor_cloexec(desc->parentend);
862
863 #ifdef PHP_WIN32
864 if (ZSTR_LEN(zmode) >= 2 && ZSTR_VAL(zmode)[1] == 'b')
865 desc->mode_flags |= O_BINARY;
866 #endif
867
868 return SUCCESS;
869 }
870
871 #ifdef PHP_WIN32
872 #define create_socketpair(socks) socketpair_win32(AF_INET, SOCK_STREAM, 0, (socks), 0)
873 #else
874 #define create_socketpair(socks) socketpair(AF_UNIX, SOCK_STREAM, 0, (socks))
875 #endif
876
set_proc_descriptor_to_socket(descriptorspec_item * desc)877 static zend_result set_proc_descriptor_to_socket(descriptorspec_item *desc)
878 {
879 php_socket_t sock[2];
880
881 if (create_socketpair(sock)) {
882 zend_string *err = php_socket_error_str(php_socket_errno());
883 php_error_docref(NULL, E_WARNING, "Unable to create socket pair: %s", ZSTR_VAL(err));
884 zend_string_release(err);
885 return FAILURE;
886 }
887
888 desc->type = DESCRIPTOR_TYPE_SOCKET;
889 desc->parentend = make_descriptor_cloexec((php_file_descriptor_t) sock[0]);
890
891 /* Pass sock[1] to child because it will never use overlapped IO on Windows. */
892 desc->childend = (php_file_descriptor_t) sock[1];
893
894 return SUCCESS;
895 }
896
set_proc_descriptor_to_file(descriptorspec_item * desc,zend_string * file_path,zend_string * file_mode)897 static zend_result set_proc_descriptor_to_file(descriptorspec_item *desc, zend_string *file_path,
898 zend_string *file_mode)
899 {
900 php_socket_t fd;
901
902 /* try a wrapper */
903 php_stream *stream = php_stream_open_wrapper(ZSTR_VAL(file_path), ZSTR_VAL(file_mode),
904 REPORT_ERRORS|STREAM_WILL_CAST, NULL);
905 if (stream == NULL) {
906 return FAILURE;
907 }
908
909 /* force into an fd */
910 if (php_stream_cast(stream, PHP_STREAM_CAST_RELEASE|PHP_STREAM_AS_FD, (void **)&fd,
911 REPORT_ERRORS) == FAILURE) {
912 return FAILURE;
913 }
914
915 #ifdef PHP_WIN32
916 desc->childend = dup_fd_as_handle((int)fd);
917 _close((int)fd);
918
919 /* Simulate the append mode by fseeking to the end of the file
920 * This introduces a potential race condition, but it is the best we can do */
921 if (strchr(ZSTR_VAL(file_mode), 'a')) {
922 SetFilePointer(desc->childend, 0, NULL, FILE_END);
923 }
924 #else
925 desc->childend = fd;
926 #endif
927 return SUCCESS;
928 }
929
dup_proc_descriptor(php_file_descriptor_t from,php_file_descriptor_t * to,zend_ulong nindex)930 static zend_result dup_proc_descriptor(php_file_descriptor_t from, php_file_descriptor_t *to,
931 zend_ulong nindex)
932 {
933 #ifdef PHP_WIN32
934 *to = dup_handle(from, TRUE, FALSE);
935 if (*to == NULL) {
936 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
937 return FAILURE;
938 }
939 #else
940 *to = dup(from);
941 if (*to < 0) {
942 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT ": %s",
943 nindex, strerror(errno));
944 return FAILURE;
945 }
946 #endif
947 return SUCCESS;
948 }
949
redirect_proc_descriptor(descriptorspec_item * desc,int target,descriptorspec_item * descriptors,int ndesc,int nindex)950 static zend_result redirect_proc_descriptor(descriptorspec_item *desc, int target,
951 descriptorspec_item *descriptors, int ndesc, int nindex)
952 {
953 php_file_descriptor_t redirect_to = PHP_INVALID_FD;
954
955 for (int i = 0; i < ndesc; i++) {
956 if (descriptors[i].index == target) {
957 redirect_to = descriptors[i].childend;
958 break;
959 }
960 }
961
962 if (redirect_to == PHP_INVALID_FD) { /* Didn't find the index we wanted */
963 if (target < 0 || target > 2) {
964 php_error_docref(NULL, E_WARNING, "Redirection target %d not found", target);
965 return FAILURE;
966 }
967
968 /* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
969 * which happens whenever an explicit override is not provided. */
970 #ifndef PHP_WIN32
971 redirect_to = target;
972 #else
973 switch (target) {
974 case 0: redirect_to = GetStdHandle(STD_INPUT_HANDLE); break;
975 case 1: redirect_to = GetStdHandle(STD_OUTPUT_HANDLE); break;
976 case 2: redirect_to = GetStdHandle(STD_ERROR_HANDLE); break;
977 EMPTY_SWITCH_DEFAULT_CASE()
978 }
979 #endif
980 }
981
982 return dup_proc_descriptor(redirect_to, &desc->childend, nindex);
983 }
984
985 /* Process one item from `$descriptorspec` argument to `proc_open` */
set_proc_descriptor_from_array(zval * descitem,descriptorspec_item * descriptors,int ndesc,int nindex,int * pty_master_fd,int * pty_slave_fd)986 static zend_result set_proc_descriptor_from_array(zval *descitem, descriptorspec_item *descriptors,
987 int ndesc, int nindex, int *pty_master_fd, int *pty_slave_fd) {
988 zend_string *ztype = get_string_parameter(descitem, 0, "handle qualifier");
989 if (!ztype) {
990 return FAILURE;
991 }
992
993 zend_string *zmode = NULL, *zfile = NULL;
994 zend_result retval = FAILURE;
995
996 if (zend_string_equals_literal(ztype, "pipe")) {
997 /* Set descriptor to pipe */
998 zmode = get_string_parameter(descitem, 1, "mode parameter for 'pipe'");
999 if (zmode == NULL) {
1000 goto finish;
1001 }
1002 retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode);
1003 } else if (zend_string_equals_literal(ztype, "socket")) {
1004 /* Set descriptor to socketpair */
1005 retval = set_proc_descriptor_to_socket(&descriptors[ndesc]);
1006 } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_FILE))) {
1007 /* Set descriptor to file */
1008 if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) {
1009 goto finish;
1010 }
1011 if ((zmode = get_string_parameter(descitem, 2, "mode parameter for 'file'")) == NULL) {
1012 goto finish;
1013 }
1014 retval = set_proc_descriptor_to_file(&descriptors[ndesc], zfile, zmode);
1015 } else if (zend_string_equals_literal(ztype, "redirect")) {
1016 /* Redirect descriptor to whatever another descriptor is set to */
1017 zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
1018 if (!ztarget) {
1019 zend_value_error("Missing redirection target");
1020 goto finish;
1021 }
1022 if (Z_TYPE_P(ztarget) != IS_LONG) {
1023 zend_value_error("Redirection target must be of type int, %s given", zend_zval_value_name(ztarget));
1024 goto finish;
1025 }
1026
1027 retval = redirect_proc_descriptor(
1028 &descriptors[ndesc], (int)Z_LVAL_P(ztarget), descriptors, ndesc, nindex);
1029 } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE))) {
1030 /* Set descriptor to blackhole (discard all data written) */
1031 retval = set_proc_descriptor_to_blackhole(&descriptors[ndesc]);
1032 } else if (zend_string_equals_literal(ztype, "pty")) {
1033 /* Set descriptor to slave end of PTY */
1034 retval = set_proc_descriptor_to_pty(&descriptors[ndesc], pty_master_fd, pty_slave_fd);
1035 } else {
1036 php_error_docref(NULL, E_WARNING, "%s is not a valid descriptor spec/mode", ZSTR_VAL(ztype));
1037 goto finish;
1038 }
1039
1040 finish:
1041 if (zmode) zend_string_release(zmode);
1042 if (zfile) zend_string_release(zfile);
1043 zend_string_release(ztype);
1044 return retval;
1045 }
1046
set_proc_descriptor_from_resource(zval * resource,descriptorspec_item * desc,int nindex)1047 static zend_result set_proc_descriptor_from_resource(zval *resource, descriptorspec_item *desc, int nindex)
1048 {
1049 /* Should be a stream - try and dup the descriptor */
1050 php_stream *stream = (php_stream*)zend_fetch_resource(Z_RES_P(resource), "stream",
1051 php_file_le_stream());
1052 if (stream == NULL) {
1053 return FAILURE;
1054 }
1055
1056 php_socket_t fd;
1057 zend_result status = php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, REPORT_ERRORS);
1058 if (status == FAILURE) {
1059 return FAILURE;
1060 }
1061
1062 #ifdef PHP_WIN32
1063 php_file_descriptor_t fd_t = (php_file_descriptor_t)_get_osfhandle(fd);
1064 #else
1065 php_file_descriptor_t fd_t = fd;
1066 #endif
1067 return dup_proc_descriptor(fd_t, &desc->childend, nindex);
1068 }
1069
1070 #ifndef PHP_WIN32
1071 #if defined(USE_POSIX_SPAWN)
close_parentends_of_pipes(posix_spawn_file_actions_t * actions,descriptorspec_item * descriptors,int ndesc)1072 static zend_result close_parentends_of_pipes(posix_spawn_file_actions_t * actions, descriptorspec_item *descriptors, int ndesc)
1073 {
1074 int r;
1075 for (int i = 0; i < ndesc; i++) {
1076 if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
1077 r = posix_spawn_file_actions_addclose(actions, descriptors[i].parentend);
1078 if (r != 0) {
1079 php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].parentend, strerror(r));
1080 return FAILURE;
1081 }
1082 }
1083 if (descriptors[i].childend != descriptors[i].index) {
1084 r = posix_spawn_file_actions_adddup2(actions, descriptors[i].childend, descriptors[i].index);
1085 if (r != 0) {
1086 php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into "
1087 "file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(r));
1088 return FAILURE;
1089 }
1090 r = posix_spawn_file_actions_addclose(actions, descriptors[i].childend);
1091 if (r != 0) {
1092 php_error_docref(NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].childend, strerror(r));
1093 return FAILURE;
1094 }
1095 }
1096 }
1097
1098 return SUCCESS;
1099 }
1100 #else
close_parentends_of_pipes(descriptorspec_item * descriptors,int ndesc)1101 static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc)
1102 {
1103 /* We are running in child process
1104 * Close the 'parent end' of pipes which were opened before forking/spawning
1105 * Also, dup() the child end of all pipes as necessary so they will use the FD
1106 * number which the user requested */
1107 for (int i = 0; i < ndesc; i++) {
1108 if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
1109 close(descriptors[i].parentend);
1110 }
1111 if (descriptors[i].childend != descriptors[i].index) {
1112 if (dup2(descriptors[i].childend, descriptors[i].index) < 0) {
1113 php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into " \
1114 "file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(errno));
1115 return FAILURE;
1116 }
1117 close(descriptors[i].childend);
1118 }
1119 }
1120
1121 return SUCCESS;
1122 }
1123 #endif
1124 #endif
1125
close_all_descriptors(descriptorspec_item * descriptors,int ndesc)1126 static void close_all_descriptors(descriptorspec_item *descriptors, int ndesc)
1127 {
1128 for (int i = 0; i < ndesc; i++) {
1129 close_descriptor(descriptors[i].childend);
1130 if (descriptors[i].parentend)
1131 close_descriptor(descriptors[i].parentend);
1132 }
1133 }
1134
efree_argv(char ** argv)1135 static void efree_argv(char **argv)
1136 {
1137 if (argv) {
1138 char **arg = argv;
1139 while (*arg != NULL) {
1140 efree(*arg);
1141 arg++;
1142 }
1143 efree(argv);
1144 }
1145 }
1146
1147 /* {{{ Execute a command, with specified files used for input/output */
PHP_FUNCTION(proc_open)1148 PHP_FUNCTION(proc_open)
1149 {
1150 zend_string *command_str;
1151 HashTable *command_ht;
1152 HashTable *descriptorspec; /* Mandatory argument */
1153 zval *pipes; /* Mandatory argument */
1154 char *cwd = NULL; /* Optional argument */
1155 size_t cwd_len = 0; /* Optional argument */
1156 zval *environment = NULL, *other_options = NULL; /* Optional arguments */
1157
1158 php_process_env env;
1159 int ndesc = 0;
1160 int i;
1161 zval *descitem = NULL;
1162 zend_string *str_index;
1163 zend_ulong nindex;
1164 descriptorspec_item *descriptors = NULL;
1165 #ifdef PHP_WIN32
1166 PROCESS_INFORMATION pi;
1167 HANDLE childHandle;
1168 STARTUPINFOW si;
1169 BOOL newprocok;
1170 DWORD dwCreateFlags = 0;
1171 UINT old_error_mode;
1172 char cur_cwd[MAXPATHLEN];
1173 wchar_t *cmdw = NULL, *cwdw = NULL, *envpw = NULL;
1174 size_t cmdw_len;
1175 bool suppress_errors = 0;
1176 bool bypass_shell = 0;
1177 bool blocking_pipes = 0;
1178 bool create_process_group = 0;
1179 bool create_new_console = 0;
1180 #else
1181 char **argv = NULL;
1182 #endif
1183 int pty_master_fd = -1, pty_slave_fd = -1;
1184 php_process_id_t child;
1185 php_process_handle *proc;
1186
1187 ZEND_PARSE_PARAMETERS_START(3, 6)
1188 Z_PARAM_ARRAY_HT_OR_STR(command_ht, command_str)
1189 Z_PARAM_ARRAY_HT(descriptorspec)
1190 Z_PARAM_ZVAL(pipes)
1191 Z_PARAM_OPTIONAL
1192 Z_PARAM_STRING_OR_NULL(cwd, cwd_len)
1193 Z_PARAM_ARRAY_OR_NULL(environment)
1194 Z_PARAM_ARRAY_OR_NULL(other_options)
1195 ZEND_PARSE_PARAMETERS_END();
1196
1197 memset(&env, 0, sizeof(env));
1198
1199 if (command_ht) {
1200 uint32_t num_elems = zend_hash_num_elements(command_ht);
1201 if (num_elems == 0) {
1202 zend_argument_value_error(1, "must have at least one element");
1203 RETURN_THROWS();
1204 }
1205
1206 #ifdef PHP_WIN32
1207 /* Automatically bypass shell if command is given as an array */
1208 bypass_shell = 1;
1209 command_str = create_win_command_from_args(command_ht);
1210 #else
1211 command_str = get_command_from_array(command_ht, &argv, num_elems);
1212 #endif
1213
1214 if (!command_str) {
1215 #ifndef PHP_WIN32
1216 efree_argv(argv);
1217 #endif
1218 RETURN_FALSE;
1219 }
1220 } else {
1221 zend_string_addref(command_str);
1222 }
1223
1224 #ifdef PHP_WIN32
1225 if (other_options) {
1226 suppress_errors = get_option(other_options, "suppress_errors", strlen("suppress_errors"));
1227 /* TODO: Deprecate in favor of array command? */
1228 bypass_shell = bypass_shell || get_option(other_options, "bypass_shell", strlen("bypass_shell"));
1229 blocking_pipes = get_option(other_options, "blocking_pipes", strlen("blocking_pipes"));
1230 create_process_group = get_option(other_options, "create_process_group", strlen("create_process_group"));
1231 create_new_console = get_option(other_options, "create_new_console", strlen("create_new_console"));
1232 }
1233 #endif
1234
1235 if (environment) {
1236 env = _php_array_to_envp(environment);
1237 }
1238
1239 descriptors = alloc_descriptor_array(descriptorspec);
1240
1241 /* Walk the descriptor spec and set up files/pipes */
1242 ZEND_HASH_FOREACH_KEY_VAL(descriptorspec, nindex, str_index, descitem) {
1243 if (str_index) {
1244 zend_argument_value_error(2, "must be an integer indexed array");
1245 goto exit_fail;
1246 }
1247
1248 descriptors[ndesc].index = (int)nindex;
1249
1250 ZVAL_DEREF(descitem);
1251 if (Z_TYPE_P(descitem) == IS_RESOURCE) {
1252 if (set_proc_descriptor_from_resource(descitem, &descriptors[ndesc], ndesc) == FAILURE) {
1253 goto exit_fail;
1254 }
1255 } else if (Z_TYPE_P(descitem) == IS_ARRAY) {
1256 if (set_proc_descriptor_from_array(descitem, descriptors, ndesc, (int)nindex,
1257 &pty_master_fd, &pty_slave_fd) == FAILURE) {
1258 goto exit_fail;
1259 }
1260 } else {
1261 zend_argument_value_error(2, "must only contain arrays and streams");
1262 goto exit_fail;
1263 }
1264 ndesc++;
1265 } ZEND_HASH_FOREACH_END();
1266
1267 #ifdef PHP_WIN32
1268 if (cwd == NULL) {
1269 char *getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN);
1270 if (!getcwd_result) {
1271 php_error_docref(NULL, E_WARNING, "Cannot get current directory");
1272 goto exit_fail;
1273 }
1274 cwd = cur_cwd;
1275 }
1276 cwdw = php_win32_cp_any_to_w(cwd);
1277 if (!cwdw) {
1278 php_error_docref(NULL, E_WARNING, "CWD conversion failed");
1279 goto exit_fail;
1280 }
1281
1282 init_startup_info(&si, descriptors, ndesc);
1283 init_process_info(&pi);
1284
1285 if (suppress_errors) {
1286 old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
1287 }
1288
1289 dwCreateFlags = NORMAL_PRIORITY_CLASS;
1290 if(strcmp(sapi_module.name, "cli") != 0) {
1291 dwCreateFlags |= CREATE_NO_WINDOW;
1292 }
1293 if (create_process_group) {
1294 dwCreateFlags |= CREATE_NEW_PROCESS_GROUP;
1295 }
1296 if (create_new_console) {
1297 dwCreateFlags |= CREATE_NEW_CONSOLE;
1298 }
1299 envpw = php_win32_cp_env_any_to_w(env.envp);
1300 if (envpw) {
1301 dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
1302 } else {
1303 if (env.envp) {
1304 php_error_docref(NULL, E_WARNING, "ENV conversion failed");
1305 goto exit_fail;
1306 }
1307 }
1308
1309 cmdw = php_win32_cp_conv_any_to_w(ZSTR_VAL(command_str), ZSTR_LEN(command_str), &cmdw_len);
1310 if (!cmdw) {
1311 php_error_docref(NULL, E_WARNING, "Command conversion failed");
1312 goto exit_fail;
1313 }
1314
1315 if (!bypass_shell) {
1316 if (convert_command_to_use_shell(&cmdw, cmdw_len) == FAILURE) {
1317 goto exit_fail;
1318 }
1319 }
1320 newprocok = CreateProcessW(NULL, cmdw, &php_proc_open_security,
1321 &php_proc_open_security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi);
1322
1323 if (suppress_errors) {
1324 SetErrorMode(old_error_mode);
1325 }
1326
1327 if (newprocok == FALSE) {
1328 DWORD dw = GetLastError();
1329 close_all_descriptors(descriptors, ndesc);
1330 php_error_docref(NULL, E_WARNING, "CreateProcess failed, error code: %u", dw);
1331 goto exit_fail;
1332 }
1333
1334 childHandle = pi.hProcess;
1335 child = pi.dwProcessId;
1336 CloseHandle(pi.hThread);
1337 #elif defined(USE_POSIX_SPAWN)
1338 posix_spawn_file_actions_t factions;
1339 int r;
1340 posix_spawn_file_actions_init(&factions);
1341
1342 if (close_parentends_of_pipes(&factions, descriptors, ndesc) == FAILURE) {
1343 posix_spawn_file_actions_destroy(&factions);
1344 close_all_descriptors(descriptors, ndesc);
1345 goto exit_fail;
1346 }
1347
1348 if (cwd) {
1349 r = posix_spawn_file_actions_addchdir_np(&factions, cwd);
1350 if (r != 0) {
1351 php_error_docref(NULL, E_WARNING, "posix_spawn_file_actions_addchdir_np() failed: %s", strerror(r));
1352 }
1353 }
1354
1355 if (argv) {
1356 r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ));
1357 } else {
1358 r = posix_spawn(&child, "/bin/sh" , &factions, NULL,
1359 (char * const[]) {"sh", "-c", ZSTR_VAL(command_str), NULL},
1360 env.envarray ? env.envarray : environ);
1361 }
1362 posix_spawn_file_actions_destroy(&factions);
1363 if (r != 0) {
1364 close_all_descriptors(descriptors, ndesc);
1365 php_error_docref(NULL, E_WARNING, "posix_spawn() failed: %s", strerror(r));
1366 goto exit_fail;
1367 }
1368 #elif HAVE_FORK
1369 /* the Unix way */
1370 child = fork();
1371
1372 if (child == 0) {
1373 /* This is the child process */
1374
1375 if (close_parentends_of_pipes(descriptors, ndesc) == FAILURE) {
1376 /* We are already in child process and can't do anything to make
1377 * `proc_open` return an error in the parent
1378 * All we can do is exit with a non-zero (error) exit code */
1379 _exit(127);
1380 }
1381
1382 if (cwd) {
1383 php_ignore_value(chdir(cwd));
1384 }
1385
1386 if (argv) {
1387 /* execvpe() is non-portable, use environ instead. */
1388 if (env.envarray) {
1389 environ = env.envarray;
1390 }
1391 execvp(ZSTR_VAL(command_str), argv);
1392 } else {
1393 if (env.envarray) {
1394 execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
1395 } else {
1396 execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
1397 }
1398 }
1399
1400 /* If execvp/execle/execl are successful, we will never reach here
1401 * Display error and exit with non-zero (error) status code */
1402 php_error_docref(NULL, E_WARNING, "Exec failed: %s", strerror(errno));
1403 _exit(127);
1404 } else if (child < 0) {
1405 /* Failed to fork() */
1406 close_all_descriptors(descriptors, ndesc);
1407 php_error_docref(NULL, E_WARNING, "Fork failed: %s", strerror(errno));
1408 goto exit_fail;
1409 }
1410 #else
1411 # error You lose (configure should not have let you get here)
1412 #endif
1413
1414 /* We forked/spawned and this is the parent */
1415
1416 pipes = zend_try_array_init(pipes);
1417 if (!pipes) {
1418 goto exit_fail;
1419 }
1420
1421 proc = (php_process_handle*) emalloc(sizeof(php_process_handle));
1422 proc->command = zend_string_copy(command_str);
1423 proc->pipes = emalloc(sizeof(zend_resource *) * ndesc);
1424 proc->npipes = ndesc;
1425 proc->child = child;
1426 #ifdef PHP_WIN32
1427 proc->childHandle = childHandle;
1428 #endif
1429 proc->env = env;
1430 #if HAVE_SYS_WAIT_H
1431 proc->has_cached_exit_wait_status = false;
1432 #endif
1433
1434 /* Clean up all the child ends and then open streams on the parent
1435 * ends, where appropriate */
1436 for (i = 0; i < ndesc; i++) {
1437 php_stream *stream = NULL;
1438
1439 close_descriptor(descriptors[i].childend);
1440
1441 if (descriptors[i].type == DESCRIPTOR_TYPE_PIPE) {
1442 char *mode_string = NULL;
1443
1444 switch (descriptors[i].mode_flags) {
1445 #ifdef PHP_WIN32
1446 case O_WRONLY|O_BINARY:
1447 mode_string = "wb";
1448 break;
1449 case O_RDONLY|O_BINARY:
1450 mode_string = "rb";
1451 break;
1452 #endif
1453 case O_WRONLY:
1454 mode_string = "w";
1455 break;
1456 case O_RDONLY:
1457 mode_string = "r";
1458 break;
1459 case O_RDWR:
1460 mode_string = "r+";
1461 break;
1462 }
1463
1464 #ifdef PHP_WIN32
1465 stream = php_stream_fopen_from_fd(_open_osfhandle((intptr_t)descriptors[i].parentend,
1466 descriptors[i].mode_flags), mode_string, NULL);
1467 php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
1468 #else
1469 stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1470 #endif
1471 } else if (descriptors[i].type == DESCRIPTOR_TYPE_SOCKET) {
1472 stream = php_stream_sock_open_from_socket((php_socket_t) descriptors[i].parentend, NULL);
1473 } else {
1474 proc->pipes[i] = NULL;
1475 }
1476
1477 if (stream) {
1478 zval retfp;
1479
1480 /* nasty hack; don't copy it */
1481 stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
1482
1483 php_stream_to_zval(stream, &retfp);
1484 add_index_zval(pipes, descriptors[i].index, &retfp);
1485
1486 proc->pipes[i] = Z_RES(retfp);
1487 Z_ADDREF(retfp);
1488 }
1489 }
1490
1491 if (1) {
1492 RETVAL_RES(zend_register_resource(proc, le_proc_open));
1493 } else {
1494 exit_fail:
1495 _php_free_envp(env);
1496 RETVAL_FALSE;
1497 }
1498
1499 zend_string_release_ex(command_str, false);
1500 #ifdef PHP_WIN32
1501 free(cwdw);
1502 free(cmdw);
1503 free(envpw);
1504 #else
1505 efree_argv(argv);
1506 #endif
1507 #ifdef HAVE_OPENPTY
1508 if (pty_master_fd != -1) {
1509 close(pty_master_fd);
1510 }
1511 if (pty_slave_fd != -1) {
1512 close(pty_slave_fd);
1513 }
1514 #endif
1515 if (descriptors) {
1516 efree(descriptors);
1517 }
1518 }
1519 /* }}} */
1520
1521 #endif /* PHP_CAN_SUPPORT_PROC_OPEN */
1522